@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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified protocol-linter entry points for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Two reporting modes share one rule registry:
|
|
5
|
+
*
|
|
6
|
+
* - {@link validateContract} — strict; runs phased validation and
|
|
7
|
+
* throws {@link ContractValidationError} on the FIRST phase that
|
|
8
|
+
* produces errors. Used at every protocol boundary (push handler,
|
|
9
|
+
* blueprint registration, future synth output gate).
|
|
10
|
+
*
|
|
11
|
+
* - {@link lintContract} — graded; runs ALL phases unconditionally
|
|
12
|
+
* and returns errors + warnings together. Used by authoring
|
|
13
|
+
* tools (synth's self-correction loop, blueprint registration's
|
|
14
|
+
* warning surfaces, future contract-author tooling).
|
|
15
|
+
*
|
|
16
|
+
* **Phased execution** (stop-at-first-error-class for `validateContract`):
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* phase 1: shape (zod wire-shape validation)
|
|
20
|
+
* phase 2: references (CTR_REF_*, CTR_DUP_NAME, CTR_RESERVED_NAME)
|
|
21
|
+
* phase 3: schema compat (CTR_SCHEMA_INCOMPAT)
|
|
22
|
+
* phase 4: hygiene (LINT_* — graded, warnings only today)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Errors are reported one phase at a time during strict validation so
|
|
26
|
+
* the LLM gets a clean signal during iterative authoring — fixing a
|
|
27
|
+
* shape bug first, then references, then schemas, then hygiene. The
|
|
28
|
+
* graded mode emits every issue at once so authoring tools can
|
|
29
|
+
* present a complete checklist.
|
|
30
|
+
*/
|
|
31
|
+
import type { DataContract } from '../types/data-contract';
|
|
32
|
+
/**
|
|
33
|
+
* Severity classification for a {@link ContractIssue}. Strict
|
|
34
|
+
* `validateContract` throws on `'error'`; graded `lintContract`
|
|
35
|
+
* surfaces both, partitioned by severity.
|
|
36
|
+
*/
|
|
37
|
+
export type ContractIssueSeverity = 'error' | 'warn';
|
|
38
|
+
/**
|
|
39
|
+
* Phase classification for a {@link ContractIssue}. Surfaced so
|
|
40
|
+
* authoring tools can render issues grouped by phase + drive the
|
|
41
|
+
* "fix one phase at a time" UX.
|
|
42
|
+
*/
|
|
43
|
+
export type ContractLintPhase = 'shape' | 'references' | 'schema-compat' | 'hygiene';
|
|
44
|
+
/**
|
|
45
|
+
* One observation about a contract from the linter's perspective.
|
|
46
|
+
*
|
|
47
|
+
* Stable-code-keyed (vs. the existing per-module `ContractViolation`
|
|
48
|
+
* shape) so authoring tools can pattern-match on `code` and drive
|
|
49
|
+
* fix workflows. The pre-existing per-module violation types
|
|
50
|
+
* (`CrossReferenceViolation`, `NameInvariantViolation`,
|
|
51
|
+
* `SchemaCompatViolation`) map into this shape via the internal
|
|
52
|
+
* conversion helpers in this file; consumers of `lintContract` /
|
|
53
|
+
* `validateContract` see only `ContractIssue`.
|
|
54
|
+
*/
|
|
55
|
+
export interface ContractIssue {
|
|
56
|
+
/** Stable error code (e.g., 'CTR_REF_NEXT_STEP', 'CTR_DUP_NAME'). */
|
|
57
|
+
readonly code: string;
|
|
58
|
+
readonly severity: ContractIssueSeverity;
|
|
59
|
+
readonly phase: ContractLintPhase;
|
|
60
|
+
/**
|
|
61
|
+
* Field path into the contract identifying the offending entry.
|
|
62
|
+
* Uses dotted JS-style notation matching the per-module
|
|
63
|
+
* `ContractViolation.field` convention
|
|
64
|
+
* (`actionSpec.archive.nextStep`).
|
|
65
|
+
*/
|
|
66
|
+
readonly path: string;
|
|
67
|
+
/** Human-readable violation prose. */
|
|
68
|
+
readonly message: string;
|
|
69
|
+
/**
|
|
70
|
+
* Optional remediation hint. Future hygiene-phase rules emit a
|
|
71
|
+
* fix recipe here ("declare `usage` on this entry"); invariant
|
|
72
|
+
* rules embed the recipe directly in `message` today.
|
|
73
|
+
*/
|
|
74
|
+
readonly fixHint?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Aggregate result of {@link lintContract}. Errors and warnings are
|
|
78
|
+
* partitioned at construction; consumers that want a flat list can
|
|
79
|
+
* concatenate.
|
|
80
|
+
*/
|
|
81
|
+
export interface ContractLintResult {
|
|
82
|
+
readonly errors: readonly ContractIssue[];
|
|
83
|
+
readonly warnings: readonly ContractIssue[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Strict-mode failure. Carries the offending phase + every issue
|
|
87
|
+
* the failing phase produced so error renderers can show every fix
|
|
88
|
+
* in one pass without re-running the linter.
|
|
89
|
+
*
|
|
90
|
+
* The `phase` field discriminates the error class: shape errors
|
|
91
|
+
* surface as a single rolled-up zod failure; reference / schema-compat
|
|
92
|
+
* errors carry one issue per violation.
|
|
93
|
+
*/
|
|
94
|
+
export declare class ContractValidationError extends Error {
|
|
95
|
+
readonly code: "contract_validation_failed";
|
|
96
|
+
readonly phase: ContractLintPhase;
|
|
97
|
+
readonly issues: readonly ContractIssue[];
|
|
98
|
+
constructor(phase: ContractLintPhase, issues: readonly ContractIssue[]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Strict-mode validator. Runs the four phases in order; throws
|
|
102
|
+
* {@link ContractValidationError} on the FIRST phase that produces
|
|
103
|
+
* errors. Used at every protocol boundary where a malformed
|
|
104
|
+
* contract is a fatal author bug.
|
|
105
|
+
*
|
|
106
|
+
* Phases run in dependency order:
|
|
107
|
+
*
|
|
108
|
+
* 1. shape — zod parse fails ⇒ nothing else makes sense
|
|
109
|
+
* 2. references — refs must resolve before schema-compat can read
|
|
110
|
+
* the referenced tool's schemas
|
|
111
|
+
* 3. schema-compat — checks rely on resolved references
|
|
112
|
+
* 4. hygiene — warnings only; never throws (handled by lintContract)
|
|
113
|
+
*
|
|
114
|
+
* Hygiene-only contracts (warnings without errors) pass the strict
|
|
115
|
+
* gate. Use {@link lintContract} when warnings matter.
|
|
116
|
+
*/
|
|
117
|
+
export declare function validateContract(contract: DataContract): void;
|
|
118
|
+
/**
|
|
119
|
+
* Graded-mode linter. Runs ALL phases unconditionally and returns
|
|
120
|
+
* errors + warnings partitioned. Suitable for authoring tools that
|
|
121
|
+
* want a complete checklist of issues + suggestions rather than the
|
|
122
|
+
* fail-fast posture of {@link validateContract}.
|
|
123
|
+
*
|
|
124
|
+
* Phase ordering still matters for diagnostics (issues are returned
|
|
125
|
+
* in phase order); but graded mode never short-circuits, so an
|
|
126
|
+
* author seeing a phase-2 reference error also sees the phase-4
|
|
127
|
+
* hygiene warnings on the same contract.
|
|
128
|
+
*/
|
|
129
|
+
export declare function lintContract(contract: DataContract): ContractLintResult;
|
|
130
|
+
//# sourceMappingURL=lint-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint-contract.d.ts","sourceRoot":"","sources":["../../src/validation/lint-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAgB3D;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB,OAAO,GACP,YAAY,GACZ,eAAe,GACf,SAAS,CAAC;AAEd;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED;;;;;;;;GAQG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAG,4BAA4B,CAAU;IACtD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;gBAE9B,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,aAAa,EAAE;CAOvE;AAwHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAiB7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,kBAAkB,CAsBvE"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified protocol-linter entry points for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Two reporting modes share one rule registry:
|
|
5
|
+
*
|
|
6
|
+
* - {@link validateContract} — strict; runs phased validation and
|
|
7
|
+
* throws {@link ContractValidationError} on the FIRST phase that
|
|
8
|
+
* produces errors. Used at every protocol boundary (push handler,
|
|
9
|
+
* blueprint registration, future synth output gate).
|
|
10
|
+
*
|
|
11
|
+
* - {@link lintContract} — graded; runs ALL phases unconditionally
|
|
12
|
+
* and returns errors + warnings together. Used by authoring
|
|
13
|
+
* tools (synth's self-correction loop, blueprint registration's
|
|
14
|
+
* warning surfaces, future contract-author tooling).
|
|
15
|
+
*
|
|
16
|
+
* **Phased execution** (stop-at-first-error-class for `validateContract`):
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* phase 1: shape (zod wire-shape validation)
|
|
20
|
+
* phase 2: references (CTR_REF_*, CTR_DUP_NAME, CTR_RESERVED_NAME)
|
|
21
|
+
* phase 3: schema compat (CTR_SCHEMA_INCOMPAT)
|
|
22
|
+
* phase 4: hygiene (LINT_* — graded, warnings only today)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Errors are reported one phase at a time during strict validation so
|
|
26
|
+
* the LLM gets a clean signal during iterative authoring — fixing a
|
|
27
|
+
* shape bug first, then references, then schemas, then hygiene. The
|
|
28
|
+
* graded mode emits every issue at once so authoring tools can
|
|
29
|
+
* present a complete checklist.
|
|
30
|
+
*/
|
|
31
|
+
import { dataContractSchema } from '../schemas/data-contract.js';
|
|
32
|
+
import { checkCrossReferences, } from './cross-references.js';
|
|
33
|
+
import { checkNameInvariants, } from './name-invariants.js';
|
|
34
|
+
import { checkSchemaCompat, } from './schema-compat-invariants.js';
|
|
35
|
+
import { checkHygiene } from './hygiene-rules.js';
|
|
36
|
+
/**
|
|
37
|
+
* Strict-mode failure. Carries the offending phase + every issue
|
|
38
|
+
* the failing phase produced so error renderers can show every fix
|
|
39
|
+
* in one pass without re-running the linter.
|
|
40
|
+
*
|
|
41
|
+
* The `phase` field discriminates the error class: shape errors
|
|
42
|
+
* surface as a single rolled-up zod failure; reference / schema-compat
|
|
43
|
+
* errors carry one issue per violation.
|
|
44
|
+
*/
|
|
45
|
+
export class ContractValidationError extends Error {
|
|
46
|
+
code = 'contract_validation_failed';
|
|
47
|
+
phase;
|
|
48
|
+
issues;
|
|
49
|
+
constructor(phase, issues) {
|
|
50
|
+
const summary = issues.map((i) => `[${i.code}] ${i.message}`).join(' | ');
|
|
51
|
+
super(`Contract validation failed at phase '${phase}': ${summary}`);
|
|
52
|
+
this.name = 'ContractValidationError';
|
|
53
|
+
this.phase = phase;
|
|
54
|
+
this.issues = issues;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Phase 1 — shape (zod wire-shape validation)
|
|
59
|
+
// =============================================================================
|
|
60
|
+
function phaseShape(contract) {
|
|
61
|
+
const parsed = dataContractSchema.safeParse(contract);
|
|
62
|
+
if (parsed.success)
|
|
63
|
+
return [];
|
|
64
|
+
return zodErrorToIssues(parsed.error);
|
|
65
|
+
}
|
|
66
|
+
function zodErrorToIssues(error) {
|
|
67
|
+
return error.issues.map((issue) => ({
|
|
68
|
+
code: zodIssueCode(issue.code),
|
|
69
|
+
severity: 'error',
|
|
70
|
+
phase: 'shape',
|
|
71
|
+
path: issue.path.join('.') || '<root>',
|
|
72
|
+
message: issue.message,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Map a zod issue code to the linter's stable code namespace. The
|
|
77
|
+
* mapping is intentionally narrow — every unknown zod code rolls up
|
|
78
|
+
* to `CTR_SHAPE_INVALID`; consumers that want zod-level granularity
|
|
79
|
+
* can re-parse with `dataContractSchema.safeParse` themselves.
|
|
80
|
+
*/
|
|
81
|
+
function zodIssueCode(zodCode) {
|
|
82
|
+
switch (zodCode) {
|
|
83
|
+
case 'invalid_type':
|
|
84
|
+
return 'CTR_SHAPE_INVALID_TYPE';
|
|
85
|
+
case 'unrecognized_keys':
|
|
86
|
+
return 'CTR_SHAPE_UNRECOGNIZED_KEYS';
|
|
87
|
+
default:
|
|
88
|
+
return 'CTR_SHAPE_INVALID';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Phase 2 — references (CTR_REF_*, CTR_DUP_NAME, CTR_RESERVED_NAME)
|
|
93
|
+
// =============================================================================
|
|
94
|
+
function phaseReferences(contract) {
|
|
95
|
+
const issues = [];
|
|
96
|
+
for (const v of checkCrossReferences(contract)) {
|
|
97
|
+
issues.push(crossRefViolationToIssue(v));
|
|
98
|
+
}
|
|
99
|
+
for (const v of checkNameInvariants(contract)) {
|
|
100
|
+
issues.push(nameInvariantViolationToIssue(v));
|
|
101
|
+
}
|
|
102
|
+
return issues;
|
|
103
|
+
}
|
|
104
|
+
function crossRefViolationToIssue(v) {
|
|
105
|
+
return {
|
|
106
|
+
code: v.code,
|
|
107
|
+
severity: 'error',
|
|
108
|
+
phase: 'references',
|
|
109
|
+
path: v.field,
|
|
110
|
+
message: v.message,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function nameInvariantViolationToIssue(v) {
|
|
114
|
+
return {
|
|
115
|
+
code: v.code,
|
|
116
|
+
severity: 'error',
|
|
117
|
+
phase: 'references',
|
|
118
|
+
path: v.field,
|
|
119
|
+
message: v.message,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// Phase 3 — schema compat (CTR_SCHEMA_INCOMPAT)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
function phaseSchemaCompat(contract) {
|
|
126
|
+
return checkSchemaCompat(contract).map(schemaCompatViolationToIssue);
|
|
127
|
+
}
|
|
128
|
+
function schemaCompatViolationToIssue(v) {
|
|
129
|
+
return {
|
|
130
|
+
code: v.code,
|
|
131
|
+
severity: 'error',
|
|
132
|
+
phase: 'schema-compat',
|
|
133
|
+
path: v.field,
|
|
134
|
+
message: v.message,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Phase 4 — hygiene (LINT_*)
|
|
139
|
+
// =============================================================================
|
|
140
|
+
function phaseHygiene(contract) {
|
|
141
|
+
return checkHygiene(contract).map(hygieneWarningToIssue);
|
|
142
|
+
}
|
|
143
|
+
function hygieneWarningToIssue(w) {
|
|
144
|
+
return {
|
|
145
|
+
code: w.code,
|
|
146
|
+
severity: 'warn',
|
|
147
|
+
phase: 'hygiene',
|
|
148
|
+
path: w.path,
|
|
149
|
+
message: w.message,
|
|
150
|
+
...(w.fixHint !== undefined ? { fixHint: w.fixHint } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// Public API
|
|
155
|
+
// =============================================================================
|
|
156
|
+
/**
|
|
157
|
+
* Strict-mode validator. Runs the four phases in order; throws
|
|
158
|
+
* {@link ContractValidationError} on the FIRST phase that produces
|
|
159
|
+
* errors. Used at every protocol boundary where a malformed
|
|
160
|
+
* contract is a fatal author bug.
|
|
161
|
+
*
|
|
162
|
+
* Phases run in dependency order:
|
|
163
|
+
*
|
|
164
|
+
* 1. shape — zod parse fails ⇒ nothing else makes sense
|
|
165
|
+
* 2. references — refs must resolve before schema-compat can read
|
|
166
|
+
* the referenced tool's schemas
|
|
167
|
+
* 3. schema-compat — checks rely on resolved references
|
|
168
|
+
* 4. hygiene — warnings only; never throws (handled by lintContract)
|
|
169
|
+
*
|
|
170
|
+
* Hygiene-only contracts (warnings without errors) pass the strict
|
|
171
|
+
* gate. Use {@link lintContract} when warnings matter.
|
|
172
|
+
*/
|
|
173
|
+
export function validateContract(contract) {
|
|
174
|
+
const shapeIssues = phaseShape(contract);
|
|
175
|
+
if (shapeIssues.length > 0) {
|
|
176
|
+
throw new ContractValidationError('shape', shapeIssues);
|
|
177
|
+
}
|
|
178
|
+
// After phase 1 we know the contract structurally parses; safe to
|
|
179
|
+
// cast through into the phase-2 / phase-3 helpers (they accept
|
|
180
|
+
// `DataContract` directly).
|
|
181
|
+
const refIssues = phaseReferences(contract);
|
|
182
|
+
if (refIssues.length > 0) {
|
|
183
|
+
throw new ContractValidationError('references', refIssues);
|
|
184
|
+
}
|
|
185
|
+
const compatIssues = phaseSchemaCompat(contract);
|
|
186
|
+
if (compatIssues.length > 0) {
|
|
187
|
+
throw new ContractValidationError('schema-compat', compatIssues);
|
|
188
|
+
}
|
|
189
|
+
// Hygiene is graded-only; not thrown by strict validator.
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Graded-mode linter. Runs ALL phases unconditionally and returns
|
|
193
|
+
* errors + warnings partitioned. Suitable for authoring tools that
|
|
194
|
+
* want a complete checklist of issues + suggestions rather than the
|
|
195
|
+
* fail-fast posture of {@link validateContract}.
|
|
196
|
+
*
|
|
197
|
+
* Phase ordering still matters for diagnostics (issues are returned
|
|
198
|
+
* in phase order); but graded mode never short-circuits, so an
|
|
199
|
+
* author seeing a phase-2 reference error also sees the phase-4
|
|
200
|
+
* hygiene warnings on the same contract.
|
|
201
|
+
*/
|
|
202
|
+
export function lintContract(contract) {
|
|
203
|
+
const issues = [];
|
|
204
|
+
issues.push(...phaseShape(contract));
|
|
205
|
+
// Phase 2 + 3 produce shape-dependent errors. When the shape
|
|
206
|
+
// phase already failed, the contract may not match the type
|
|
207
|
+
// signatures these phases assume — skip downstream phases in that
|
|
208
|
+
// case to avoid throwing during the lint run. Authoring tools see
|
|
209
|
+
// "fix shape first" via the shape issues; once those are clean
|
|
210
|
+
// the next `lintContract` run reaches the deeper phases.
|
|
211
|
+
if (issues.length === 0) {
|
|
212
|
+
issues.push(...phaseReferences(contract));
|
|
213
|
+
issues.push(...phaseSchemaCompat(contract));
|
|
214
|
+
}
|
|
215
|
+
issues.push(...phaseHygiene(contract));
|
|
216
|
+
const errors = [];
|
|
217
|
+
const warnings = [];
|
|
218
|
+
for (const issue of issues) {
|
|
219
|
+
if (issue.severity === 'error')
|
|
220
|
+
errors.push(issue);
|
|
221
|
+
else
|
|
222
|
+
warnings.push(issue);
|
|
223
|
+
}
|
|
224
|
+
return { errors, warnings };
|
|
225
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name-uniqueness and reserved-namespace invariants for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Two stable error codes:
|
|
5
|
+
*
|
|
6
|
+
* - `CTR_DUP_NAME` — the same key appears in two or more of
|
|
7
|
+
* `actionSpec`, `streamSpec`, `contextSpec`.
|
|
8
|
+
* Collisions are author bugs: the boilerplate
|
|
9
|
+
* generator emits identifiers from these keys
|
|
10
|
+
* (action handlers, channel subscribers, slot
|
|
11
|
+
* state hooks), and a collision either shadows
|
|
12
|
+
* or compiles to ambiguous source. The agent's
|
|
13
|
+
* downstream reasoning also fragments —
|
|
14
|
+
* "submit" can't be both a discrete event AND
|
|
15
|
+
* observable state without confusing the
|
|
16
|
+
* actions-vs-context placement rule.
|
|
17
|
+
*
|
|
18
|
+
* - `CTR_RESERVED_NAME` — a key on `actionSpec` or `contextSpec`
|
|
19
|
+
* starts with the `_ggui:` reserved prefix.
|
|
20
|
+
* `streamSpec` already enforces this in
|
|
21
|
+
* `validateContractStructure` (server-owned
|
|
22
|
+
* reserved channels can't be agent-declared);
|
|
23
|
+
* this invariant extends the rule to the other
|
|
24
|
+
* two inbound specs so the reserved namespace
|
|
25
|
+
* is uniformly off-limits to authors. Today no
|
|
26
|
+
* reserved actions or context slots exist, but
|
|
27
|
+
* the protocol reserves the namespace forward
|
|
28
|
+
* for runtime-owned signals.
|
|
29
|
+
*
|
|
30
|
+
* Pure checks; return violations rather than throwing. Callers that
|
|
31
|
+
* want fail-fast semantics use {@link assertNameInvariants}. Folded
|
|
32
|
+
* into `validateContractStructure` so the structural-validator surface
|
|
33
|
+
* picks up these rules without per-caller wiring.
|
|
34
|
+
*
|
|
35
|
+
* Companion to {@link CrossReferenceError} in `./cross-references` —
|
|
36
|
+
* the two modules ship the "phase 2: references" rule registry.
|
|
37
|
+
*/
|
|
38
|
+
import type { DataContract } from '../types/data-contract';
|
|
39
|
+
import type { ContractViolation } from './contract-validator';
|
|
40
|
+
/**
|
|
41
|
+
* Stable error code for collisions across the three inbound spec maps
|
|
42
|
+
* (`actionSpec` / `streamSpec` / `contextSpec`). The boilerplate
|
|
43
|
+
* generator emits identifiers from these keys; a collision is an
|
|
44
|
+
* author bug that the protocol catches at push.
|
|
45
|
+
*/
|
|
46
|
+
export declare const CTR_DUP_NAME = "CTR_DUP_NAME";
|
|
47
|
+
/**
|
|
48
|
+
* Stable error code for keys in the `_ggui:` reserved namespace on
|
|
49
|
+
* `actionSpec` or `contextSpec`. `streamSpec` already enforces the
|
|
50
|
+
* same rule in `validateContractStructure`.
|
|
51
|
+
*/
|
|
52
|
+
export declare const CTR_RESERVED_NAME = "CTR_RESERVED_NAME";
|
|
53
|
+
/**
|
|
54
|
+
* Name-invariant violation. Adds a stable `code` field on top of
|
|
55
|
+
* `ContractViolation` so consumers can switch on the code rather than
|
|
56
|
+
* pattern-matching message strings.
|
|
57
|
+
*/
|
|
58
|
+
export interface NameInvariantViolation extends ContractViolation {
|
|
59
|
+
code: typeof CTR_DUP_NAME | typeof CTR_RESERVED_NAME;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate that no name appears in more than one of `actionSpec`,
|
|
63
|
+
* `streamSpec`, `contextSpec`. Emits one violation per colliding name
|
|
64
|
+
* (not one per spec the name appears in) so the agent sees a single
|
|
65
|
+
* actionable line per collision.
|
|
66
|
+
*
|
|
67
|
+
* Order is stable: collisions are reported in the order names first
|
|
68
|
+
* appear when scanning `actionSpec` → `streamSpec` → `contextSpec`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkNameCollisions(contract: DataContract): NameInvariantViolation[];
|
|
71
|
+
/**
|
|
72
|
+
* Validate that no `actionSpec` or `contextSpec` key uses the
|
|
73
|
+
* `_ggui:` reserved namespace. `streamSpec` reserved-channel rejection
|
|
74
|
+
* lives in `validateContractStructure` (the reserved namespace there
|
|
75
|
+
* carries server-side semantics like the `_ggui:contract-error`
|
|
76
|
+
* channel); this invariant extends the rule uniformly across the
|
|
77
|
+
* other two inbound spec maps.
|
|
78
|
+
*
|
|
79
|
+
* Future runtime-owned action or context signals would carry the
|
|
80
|
+
* `_ggui:` prefix and would be emitted by the runtime, not declared
|
|
81
|
+
* by authors. Today no such signals exist, but the prefix is reserved
|
|
82
|
+
* forward.
|
|
83
|
+
*/
|
|
84
|
+
export declare function checkReservedNames(contract: DataContract): NameInvariantViolation[];
|
|
85
|
+
/**
|
|
86
|
+
* Run every name-invariant check. Returns the aggregated violation
|
|
87
|
+
* list — order is stable: collisions first, reserved names second.
|
|
88
|
+
*
|
|
89
|
+
* Pure check; doesn't throw. Callers that want fail-fast semantics
|
|
90
|
+
* use {@link assertNameInvariants}.
|
|
91
|
+
*/
|
|
92
|
+
export declare function checkNameInvariants(contract: DataContract): NameInvariantViolation[];
|
|
93
|
+
/**
|
|
94
|
+
* Throwable form of {@link checkNameInvariants}. Use at protocol
|
|
95
|
+
* boundaries where a name collision or reserved-namespace use is a
|
|
96
|
+
* contract bug the caller must fix (push handler, blueprint
|
|
97
|
+
* registration).
|
|
98
|
+
*
|
|
99
|
+
* Carries the full violation list so error renderers can show every
|
|
100
|
+
* offending name in one pass instead of fix-and-retry per-field.
|
|
101
|
+
*/
|
|
102
|
+
export declare class NameInvariantError extends Error {
|
|
103
|
+
readonly code: "name_invariant_violation";
|
|
104
|
+
readonly violations: readonly NameInvariantViolation[];
|
|
105
|
+
constructor(violations: readonly NameInvariantViolation[]);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Throw-on-violation wrapper around {@link checkNameInvariants}.
|
|
109
|
+
* No-op when the contract's names are consistent.
|
|
110
|
+
*
|
|
111
|
+
* Designed to slot alongside `assertCrossReferences` at push time:
|
|
112
|
+
* cross-reference invariants catch dangling pointers between specs;
|
|
113
|
+
* name invariants catch malformed name spaces within specs. Both
|
|
114
|
+
* surface author-recoverable failures before any state mutation.
|
|
115
|
+
*/
|
|
116
|
+
export declare function assertNameInvariants(contract: DataContract): void;
|
|
117
|
+
//# sourceMappingURL=name-invariants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-invariants.d.ts","sourceRoot":"","sources":["../../src/validation/name-invariants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAM9D;;;;;GAKG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,IAAI,EAAE,OAAO,YAAY,GAAG,OAAO,iBAAiB,CAAC;CACtD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,sBAAsB,EAAE,CA6B1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,YAAY,GACrB,sBAAsB,EAAE,CAmB1B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,sBAAsB,EAAE,CAE1B;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAG,0BAA0B,CAAU;IACpD,QAAQ,CAAC,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAC;gBAE3C,UAAU,EAAE,SAAS,sBAAsB,EAAE;CAQ1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAKjE"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name-uniqueness and reserved-namespace invariants for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Two stable error codes:
|
|
5
|
+
*
|
|
6
|
+
* - `CTR_DUP_NAME` — the same key appears in two or more of
|
|
7
|
+
* `actionSpec`, `streamSpec`, `contextSpec`.
|
|
8
|
+
* Collisions are author bugs: the boilerplate
|
|
9
|
+
* generator emits identifiers from these keys
|
|
10
|
+
* (action handlers, channel subscribers, slot
|
|
11
|
+
* state hooks), and a collision either shadows
|
|
12
|
+
* or compiles to ambiguous source. The agent's
|
|
13
|
+
* downstream reasoning also fragments —
|
|
14
|
+
* "submit" can't be both a discrete event AND
|
|
15
|
+
* observable state without confusing the
|
|
16
|
+
* actions-vs-context placement rule.
|
|
17
|
+
*
|
|
18
|
+
* - `CTR_RESERVED_NAME` — a key on `actionSpec` or `contextSpec`
|
|
19
|
+
* starts with the `_ggui:` reserved prefix.
|
|
20
|
+
* `streamSpec` already enforces this in
|
|
21
|
+
* `validateContractStructure` (server-owned
|
|
22
|
+
* reserved channels can't be agent-declared);
|
|
23
|
+
* this invariant extends the rule to the other
|
|
24
|
+
* two inbound specs so the reserved namespace
|
|
25
|
+
* is uniformly off-limits to authors. Today no
|
|
26
|
+
* reserved actions or context slots exist, but
|
|
27
|
+
* the protocol reserves the namespace forward
|
|
28
|
+
* for runtime-owned signals.
|
|
29
|
+
*
|
|
30
|
+
* Pure checks; return violations rather than throwing. Callers that
|
|
31
|
+
* want fail-fast semantics use {@link assertNameInvariants}. Folded
|
|
32
|
+
* into `validateContractStructure` so the structural-validator surface
|
|
33
|
+
* picks up these rules without per-caller wiring.
|
|
34
|
+
*
|
|
35
|
+
* Companion to {@link CrossReferenceError} in `./cross-references` —
|
|
36
|
+
* the two modules ship the "phase 2: references" rule registry.
|
|
37
|
+
*/
|
|
38
|
+
import { RESERVED_CHANNEL_PREFIX, isReservedChannelName, } from './reserved-channels.js';
|
|
39
|
+
/**
|
|
40
|
+
* Stable error code for collisions across the three inbound spec maps
|
|
41
|
+
* (`actionSpec` / `streamSpec` / `contextSpec`). The boilerplate
|
|
42
|
+
* generator emits identifiers from these keys; a collision is an
|
|
43
|
+
* author bug that the protocol catches at push.
|
|
44
|
+
*/
|
|
45
|
+
export const CTR_DUP_NAME = 'CTR_DUP_NAME';
|
|
46
|
+
/**
|
|
47
|
+
* Stable error code for keys in the `_ggui:` reserved namespace on
|
|
48
|
+
* `actionSpec` or `contextSpec`. `streamSpec` already enforces the
|
|
49
|
+
* same rule in `validateContractStructure`.
|
|
50
|
+
*/
|
|
51
|
+
export const CTR_RESERVED_NAME = 'CTR_RESERVED_NAME';
|
|
52
|
+
/** Spec maps that share the inbound name namespace. */
|
|
53
|
+
const SPEC_FIELDS = ['actionSpec', 'streamSpec', 'contextSpec'];
|
|
54
|
+
/**
|
|
55
|
+
* Validate that no name appears in more than one of `actionSpec`,
|
|
56
|
+
* `streamSpec`, `contextSpec`. Emits one violation per colliding name
|
|
57
|
+
* (not one per spec the name appears in) so the agent sees a single
|
|
58
|
+
* actionable line per collision.
|
|
59
|
+
*
|
|
60
|
+
* Order is stable: collisions are reported in the order names first
|
|
61
|
+
* appear when scanning `actionSpec` → `streamSpec` → `contextSpec`.
|
|
62
|
+
*/
|
|
63
|
+
export function checkNameCollisions(contract) {
|
|
64
|
+
// Build name → list of spec fields where it appears
|
|
65
|
+
const byName = new Map();
|
|
66
|
+
for (const field of SPEC_FIELDS) {
|
|
67
|
+
const spec = contract[field];
|
|
68
|
+
if (!spec || typeof spec !== 'object')
|
|
69
|
+
continue;
|
|
70
|
+
for (const name of Object.keys(spec)) {
|
|
71
|
+
const existing = byName.get(name);
|
|
72
|
+
if (existing) {
|
|
73
|
+
existing.push(field);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
byName.set(name, [field]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const violations = [];
|
|
81
|
+
for (const [name, fields] of byName) {
|
|
82
|
+
if (fields.length < 2)
|
|
83
|
+
continue;
|
|
84
|
+
violations.push({
|
|
85
|
+
code: CTR_DUP_NAME,
|
|
86
|
+
field: `${fields[0]}.${name}`,
|
|
87
|
+
message: `Name '${name}' is declared in multiple specs: ${fields.join(', ')}. Each name MUST appear in exactly one of actionSpec / streamSpec / contextSpec — the boilerplate generator emits identifiers from these keys and collisions produce shadowed or ambiguous source.`,
|
|
88
|
+
expected: 'unique name across inbound specs',
|
|
89
|
+
received: fields.join(' + '),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return violations;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate that no `actionSpec` or `contextSpec` key uses the
|
|
96
|
+
* `_ggui:` reserved namespace. `streamSpec` reserved-channel rejection
|
|
97
|
+
* lives in `validateContractStructure` (the reserved namespace there
|
|
98
|
+
* carries server-side semantics like the `_ggui:contract-error`
|
|
99
|
+
* channel); this invariant extends the rule uniformly across the
|
|
100
|
+
* other two inbound spec maps.
|
|
101
|
+
*
|
|
102
|
+
* Future runtime-owned action or context signals would carry the
|
|
103
|
+
* `_ggui:` prefix and would be emitted by the runtime, not declared
|
|
104
|
+
* by authors. Today no such signals exist, but the prefix is reserved
|
|
105
|
+
* forward.
|
|
106
|
+
*/
|
|
107
|
+
export function checkReservedNames(contract) {
|
|
108
|
+
const violations = [];
|
|
109
|
+
for (const field of ['actionSpec', 'contextSpec']) {
|
|
110
|
+
const spec = contract[field];
|
|
111
|
+
if (!spec || typeof spec !== 'object')
|
|
112
|
+
continue;
|
|
113
|
+
for (const name of Object.keys(spec)) {
|
|
114
|
+
if (!isReservedChannelName(name))
|
|
115
|
+
continue;
|
|
116
|
+
violations.push({
|
|
117
|
+
code: CTR_RESERVED_NAME,
|
|
118
|
+
field: `${field}.${name}`,
|
|
119
|
+
message: `${field}.${name} is in the reserved '${RESERVED_CHANNEL_PREFIX}' namespace — names starting with that prefix are reserved for runtime-owned signals and cannot be agent-declared.`,
|
|
120
|
+
expected: `name not starting with '${RESERVED_CHANNEL_PREFIX}'`,
|
|
121
|
+
received: name,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return violations;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Run every name-invariant check. Returns the aggregated violation
|
|
129
|
+
* list — order is stable: collisions first, reserved names second.
|
|
130
|
+
*
|
|
131
|
+
* Pure check; doesn't throw. Callers that want fail-fast semantics
|
|
132
|
+
* use {@link assertNameInvariants}.
|
|
133
|
+
*/
|
|
134
|
+
export function checkNameInvariants(contract) {
|
|
135
|
+
return [...checkNameCollisions(contract), ...checkReservedNames(contract)];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Throwable form of {@link checkNameInvariants}. Use at protocol
|
|
139
|
+
* boundaries where a name collision or reserved-namespace use is a
|
|
140
|
+
* contract bug the caller must fix (push handler, blueprint
|
|
141
|
+
* registration).
|
|
142
|
+
*
|
|
143
|
+
* Carries the full violation list so error renderers can show every
|
|
144
|
+
* offending name in one pass instead of fix-and-retry per-field.
|
|
145
|
+
*/
|
|
146
|
+
export class NameInvariantError extends Error {
|
|
147
|
+
code = 'name_invariant_violation';
|
|
148
|
+
violations;
|
|
149
|
+
constructor(violations) {
|
|
150
|
+
const summary = violations
|
|
151
|
+
.map((v) => `[${v.code}] ${v.message}`)
|
|
152
|
+
.join(' | ');
|
|
153
|
+
super(`Contract name-invariant check failed: ${summary}`);
|
|
154
|
+
this.name = 'NameInvariantError';
|
|
155
|
+
this.violations = violations;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Throw-on-violation wrapper around {@link checkNameInvariants}.
|
|
160
|
+
* No-op when the contract's names are consistent.
|
|
161
|
+
*
|
|
162
|
+
* Designed to slot alongside `assertCrossReferences` at push time:
|
|
163
|
+
* cross-reference invariants catch dangling pointers between specs;
|
|
164
|
+
* name invariants catch malformed name spaces within specs. Both
|
|
165
|
+
* surface author-recoverable failures before any state mutation.
|
|
166
|
+
*/
|
|
167
|
+
export function assertNameInvariants(contract) {
|
|
168
|
+
const violations = checkNameInvariants(contract);
|
|
169
|
+
if (violations.length > 0) {
|
|
170
|
+
throw new NameInvariantError(violations);
|
|
171
|
+
}
|
|
172
|
+
}
|