@rayspec/spec 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/dist/brace-params.d.ts +32 -0
- package/dist/brace-params.d.ts.map +1 -0
- package/dist/brace-params.js +89 -0
- package/dist/brace-params.js.map +1 -0
- package/dist/detect.d.ts +44 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +72 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +204 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +165 -0
- package/dist/errors.js.map +1 -0
- package/dist/export.d.ts +54 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +121 -0
- package/dist/export.js.map +1 -0
- package/dist/grammar.d.ts +569 -0
- package/dist/grammar.d.ts.map +1 -0
- package/dist/grammar.js +503 -0
- package/dist/grammar.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.d.ts +79 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +823 -0
- package/dist/lint.js.map +1 -0
- package/dist/parse.d.ts +8 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +118 -0
- package/dist/parse.js.map +1 -0
- package/dist/product-events.d.ts +73 -0
- package/dist/product-events.d.ts.map +1 -0
- package/dist/product-events.js +42 -0
- package/dist/product-events.js.map +1 -0
- package/dist/product-grammar.d.ts +948 -0
- package/dist/product-grammar.d.ts.map +1 -0
- package/dist/product-grammar.js +581 -0
- package/dist/product-grammar.js.map +1 -0
- package/dist/product-lint.d.ts +74 -0
- package/dist/product-lint.d.ts.map +1 -0
- package/dist/product-lint.js +821 -0
- package/dist/product-lint.js.map +1 -0
- package/dist/product-parse.d.ts +8 -0
- package/dist/product-parse.d.ts.map +1 -0
- package/dist/product-parse.js +119 -0
- package/dist/product-parse.js.map +1 -0
- package/dist/product-scope.d.ts +53 -0
- package/dist/product-scope.d.ts.map +1 -0
- package/dist/product-scope.js +55 -0
- package/dist/product-scope.js.map +1 -0
- package/dist/product-views-lint.d.ts +14 -0
- package/dist/product-views-lint.d.ts.map +1 -0
- package/dist/product-views-lint.js +669 -0
- package/dist/product-views-lint.js.map +1 -0
- package/dist/product-views.d.ts +426 -0
- package/dist/product-views.d.ts.map +1 -0
- package/dist/product-views.js +317 -0
- package/dist/product-views.js.map +1 -0
- package/package.json +34 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecError — the closed-code error envelope for the RaySpec config grammar.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the auth-core `ErrorCode`/`ErrorEnvelope` pattern (packages/auth-core/src/errors.ts):
|
|
5
|
+
* a CLOSED Zod enum of codes + a flat `{ code, message, path? }` envelope. Closed-by-construction
|
|
6
|
+
* so a fresh session reading a parse failure sees a finite, documented vocabulary — never a free
|
|
7
|
+
* string. `parseSpec`/`lintSpec` aggregate the FULL list of violations (not the first), so an
|
|
8
|
+
* author sees every problem in one pass.
|
|
9
|
+
*
|
|
10
|
+
* `path` is a JSON path into the spec (dot/bracket notation, e.g. `agents[0].backend`) so a
|
|
11
|
+
* violation points at the exact offending node. It is absent for whole-document failures
|
|
12
|
+
* (`yaml_parse_error`, `unsupported_version`) where no in-document path applies.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
/**
|
|
16
|
+
* The CLOSED set of spec-error codes. Every parse/lint failure carries exactly one of these.
|
|
17
|
+
*
|
|
18
|
+
* - `yaml_parse_error` — the raw text is not valid YAML (the `yaml` lib threw).
|
|
19
|
+
* - `unsupported_version` — `version` is missing or not the supported literal ('1.0').
|
|
20
|
+
* - `schema_violation` — a Zod shape failure that is not a pure unknown-key rejection
|
|
21
|
+
* (wrong type, missing required field, bad enum value, …).
|
|
22
|
+
* - `unknown_field` — a `.strict()` unknown-key rejection (fail-closed: any extra key).
|
|
23
|
+
* - `dangling_ref` — a cross-reference points at an id/name that is not declared.
|
|
24
|
+
* - `duplicate_name` — two entries in one section share an id/name.
|
|
25
|
+
* - `capability_violation` — an agent demands a capability its chosen backend lacks
|
|
26
|
+
* (via core `validateSpec`; e.g. native structured output on pi).
|
|
27
|
+
* - `invalid_embedded_schema`— an embedded JSON-Schema (tool `parameters`/`outputSchema`,
|
|
28
|
+
* agent `outputSchema.schema`) failed to compile through Ajv2020.
|
|
29
|
+
* - `reserved_column_name` — a store declares a business column whose name collides with a
|
|
30
|
+
* tenancy/GDPR column the table generator injects (`id`,
|
|
31
|
+
* `tenant_id`, `created_at`, `deleted_at`, `retention_days`,
|
|
32
|
+
* `region`) — fail-closed against a shadow/tenancy collision.
|
|
33
|
+
* - `reserved_query_keyword` — a store declares a business column whose name is one of the
|
|
34
|
+
* list-query CONTROL keywords (`order`, `after`, `limit`). Those keys
|
|
35
|
+
* steer the declarative `list` route's sorting + keyset pagination, so a
|
|
36
|
+
* column of that name would be silently un-equality-filterable AND would
|
|
37
|
+
* emit a DUPLICATE OpenAPI query parameter (control param + per-column
|
|
38
|
+
* filter param, same name+location) → an invalid OpenAPI 3.1 document.
|
|
39
|
+
* Rename the business column.
|
|
40
|
+
* - `frontend_route_collision` — a declared static frontend mount's `route` collides with another
|
|
41
|
+
* mount, with a declared `api[].path`, or with a reserved system prefix
|
|
42
|
+
* (`/v1`, `/health`, `/oidc`) — the static mount would either shadow or be
|
|
43
|
+
* shadowed by that route. Root `/` is exempt (it coexists with `/v1/*` via
|
|
44
|
+
* registration order). Rename the frontend route.
|
|
45
|
+
* - `frontend_dir_missing` — a declared frontend `dir` does not resolve to a readable directory of
|
|
46
|
+
* built assets (surfaced by `doctor`, which checks the filesystem).
|
|
47
|
+
* - `fk_cycle` — the declared stores form a CIRCULAR foreign-key reference (store A
|
|
48
|
+
* references B and B references A, directly or transitively). Such a set is
|
|
49
|
+
* UNORDERABLE: each store's FK ADD needs its parent table to already exist,
|
|
50
|
+
* so no CREATE order satisfies every FK. Rejected fail-closed at config time
|
|
51
|
+
* rather than surfacing as a cryptic `42P01 relation does not exist` at
|
|
52
|
+
* apply. Self-references are EXEMPT (a self-FK applies after its own CREATE).
|
|
53
|
+
*
|
|
54
|
+
* PRODUCT-YAML codes — used ONLY by the Product-YAML validation path (`parseProductSpec`,
|
|
55
|
+
* `product-lint.ts`). They share this closed envelope so a fresh session sees ONE error vocabulary
|
|
56
|
+
* across both document families; the RaySpec path never emits them.
|
|
57
|
+
* - `no_code_in_yaml` — a code/handler/SQL/shell key or an inline-code string value appears
|
|
58
|
+
* in a Product-YAML doc: implementation belongs in Tier A/B, not
|
|
59
|
+
* in product meaning. The message names what the offending key should be.
|
|
60
|
+
* - `provider_native_leak` — a provider-native wire blob (raw request/response payload) or a
|
|
61
|
+
* provider/model policy field / provider name leaked into the executable
|
|
62
|
+
* `workflows`/`agents` graph (which must stay provider-neutral so it
|
|
63
|
+
* compiles through the workflow bridge). Provider policy is only allowed
|
|
64
|
+
* in `capabilities[].provider_policy` / `deployment_overrides`.
|
|
65
|
+
* - `invalid_capability_status` — RESERVED (closed-code discipline). The earlier doc-level
|
|
66
|
+
* rejection of `status:'available'` was retired (the Tier B
|
|
67
|
+
* runtime is wired now); capability WIREDNESS is enforced at the deploy
|
|
68
|
+
* composition (fail-closed `unsupported_spec`), not by the parser.
|
|
69
|
+
* - `invalid_contract` — a `contracts[]` schema uses a key/type outside the closed, declarative
|
|
70
|
+
* JSON-Schema-like vocabulary (no functions/transforms/computed
|
|
71
|
+
* expressions/provider-native shapes).
|
|
72
|
+
* - `prompt_execution_claim` — a Product-YAML `workflows`/`agents` graph STRING claims prompt/LLM
|
|
73
|
+
* EXECUTION (`llm call`, `agent call`, `prompt execution`, `execute
|
|
74
|
+
* prompt`). Mirrors the workflow-bridge's `prompt_execution_claim` so a
|
|
75
|
+
* doc that validates here also compiles through the bridge (anti-drift;
|
|
76
|
+
* parity-tested). Prompt/agent execution is a Tier-B runtime concern.
|
|
77
|
+
* - `production_execution_claim` — a graph STRING claims production EXECUTION (`production_ready`,
|
|
78
|
+
* `production execution`, `prod runtime`). Mirrors the bridge's
|
|
79
|
+
* `production_execution_claim` (parity-tested): a product doc declares
|
|
80
|
+
* meaning, not that it EXECUTES in production.
|
|
81
|
+
* - `invalid_dependency_order` — a workflow step's `depends_on` references a step that is NOT declared
|
|
82
|
+
* before it (a forward/self reference). Declaration-order is required, which
|
|
83
|
+
* structurally forbids dependency CYCLES (a cycle needs a forward edge).
|
|
84
|
+
* - `invalid_view` — a view declaration violates the view semantics (product-views-lint):
|
|
85
|
+
* a source/contract conflation (a source ref that names a
|
|
86
|
+
* contract instead of a store/artifact/capability contract), a read/shape
|
|
87
|
+
* context violation (page fields outside `list` mode, group outside `collect`,
|
|
88
|
+
* …), incomplete param coverage, a shape that does not conform to its declared
|
|
89
|
+
* response contract, a reserved (`__proto__`-class) name, or a pagination law
|
|
90
|
+
* violation. Every mis-declared view construct is rejected — never skipped.
|
|
91
|
+
* - `invalid_store` — a declared product store / store-step declaration violates the store
|
|
92
|
+
* semantics: a store name colliding with a
|
|
93
|
+
* derived collection store, a column name on the graph key denylist (it could
|
|
94
|
+
* never be referenced from a workflow step), a key naming an undeclared or
|
|
95
|
+
* nullable column, a store step targeting an undeclared store, a filter/values
|
|
96
|
+
* column outside the store's declared columns, a write omitting the conflict-
|
|
97
|
+
* key column, or store vocabulary on a non-store step type. Fail-closed.
|
|
98
|
+
*/
|
|
99
|
+
export const SpecErrorCode = z.enum([
|
|
100
|
+
'yaml_parse_error',
|
|
101
|
+
'unsupported_version',
|
|
102
|
+
'schema_violation',
|
|
103
|
+
'unknown_field',
|
|
104
|
+
'dangling_ref',
|
|
105
|
+
'duplicate_name',
|
|
106
|
+
'capability_violation',
|
|
107
|
+
'invalid_embedded_schema',
|
|
108
|
+
'reserved_column_name',
|
|
109
|
+
'reserved_query_keyword',
|
|
110
|
+
'frontend_route_collision',
|
|
111
|
+
'frontend_dir_missing',
|
|
112
|
+
'fk_cycle',
|
|
113
|
+
'no_code_in_yaml',
|
|
114
|
+
'provider_native_leak',
|
|
115
|
+
'invalid_capability_status',
|
|
116
|
+
'invalid_contract',
|
|
117
|
+
'prompt_execution_claim',
|
|
118
|
+
'production_execution_claim',
|
|
119
|
+
'invalid_dependency_order',
|
|
120
|
+
'invalid_view',
|
|
121
|
+
'invalid_store',
|
|
122
|
+
]);
|
|
123
|
+
/** A single fail-closed spec violation (closed code + message + optional JSON path). */
|
|
124
|
+
export const SpecError = z.object({
|
|
125
|
+
code: SpecErrorCode,
|
|
126
|
+
message: z.string(),
|
|
127
|
+
/** JSON path into the spec document (e.g. `agents[0].backend`); absent for whole-doc failures. */
|
|
128
|
+
path: z.string().optional(),
|
|
129
|
+
});
|
|
130
|
+
/** Construct a SpecError (path omitted when undefined so the envelope stays minimal). */
|
|
131
|
+
export function specError(code, message, path) {
|
|
132
|
+
return path !== undefined ? { code, message, path } : { code, message };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The CLOSED set of NON-FATAL spec-warning codes. A warning flags a documented, deliberately-permitted
|
|
136
|
+
* interaction the author should be AWARE of — it does NOT fail `doctor`/`plan` (unlike a `SpecError`).
|
|
137
|
+
* Kept a distinct closed vocabulary from `SpecErrorCode` so a fresh session never confuses "advisory"
|
|
138
|
+
* with "fail-closed".
|
|
139
|
+
*
|
|
140
|
+
* - `softdelete_fk_restrict` — a `softDelete` store is the TARGET of a `restrict` business-key
|
|
141
|
+
* (`referencesColumn`) foreign key. Soft-deleting such a parent is an
|
|
142
|
+
* `UPDATE(deleted_at)` that does NOT fire the database ON DELETE restrict,
|
|
143
|
+
* so children keep pointing at the tombstoned row — the restrict guarantee
|
|
144
|
+
* only binds on a HARD delete. This is a permitted, documented interaction.
|
|
145
|
+
* - `fk_forward_reference` — a store declares a foreign key onto another store declared LATER in the
|
|
146
|
+
* `stores` array. The product-SQL generator topo-sorts stores so the parent
|
|
147
|
+
* table is created before the child's FK is added, so a forward reference
|
|
148
|
+
* still applies cleanly; this advisory notes only that the declared order
|
|
149
|
+
* relies on that reordering (declaring the parent first makes it explicit).
|
|
150
|
+
* Acyclic by construction — a true cycle is the fail-closed `fk_cycle`
|
|
151
|
+
* error, never a warning.
|
|
152
|
+
*/
|
|
153
|
+
export const SpecWarningCode = z.enum(['softdelete_fk_restrict', 'fk_forward_reference']);
|
|
154
|
+
/** A single NON-FATAL spec warning (closed code + message + optional JSON path). Never fails a parse. */
|
|
155
|
+
export const SpecWarning = z.object({
|
|
156
|
+
code: SpecWarningCode,
|
|
157
|
+
message: z.string(),
|
|
158
|
+
/** JSON path into the spec document (e.g. `stores[0].softDelete`); absent for whole-doc warnings. */
|
|
159
|
+
path: z.string().optional(),
|
|
160
|
+
});
|
|
161
|
+
/** Construct a SpecWarning (path omitted when undefined so the envelope stays minimal). */
|
|
162
|
+
export function specWarning(code, message, path) {
|
|
163
|
+
return path !== undefined ? { code, message, path } : { code, message };
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC;IAClC,kBAAkB;IAClB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,sBAAsB;IACtB,yBAAyB;IACzB,sBAAsB;IACtB,wBAAwB;IACxB,0BAA0B;IAC1B,sBAAsB;IACtB,UAAU;IACV,iBAAiB;IACjB,sBAAsB;IACtB,2BAA2B;IAC3B,kBAAkB;IAClB,wBAAwB;IACxB,4BAA4B;IAC5B,0BAA0B;IAC1B,cAAc;IACd,eAAe;CAChB,CAAC,CAAC;AAGH,wFAAwF;AACxF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,kGAAkG;IAClG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,IAAmB,EAAE,OAAe,EAAE,IAAa;IAC3E,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC1E,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,wBAAwB,EAAE,sBAAsB,CAAC,CAAC,CAAC;AAG1F,yGAAyG;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,qGAAqG;IACrG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,IAAqB,EAAE,OAAe,EAAE,IAAa;IAC/E,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC1E,CAAC"}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export the `RaySpec` grammar as a JSON-Schema (draft-2020-12) object. Every STRICT grammar
|
|
3
|
+
* object level carries `additionalProperties:false` (the fail-closed shape an Ajv2020 instance
|
|
4
|
+
* enforces byte-for-byte); the three embedded JSON-Schema slots (tool `parameters`/`outputSchema`,
|
|
5
|
+
* agent `outputSchema.schema`) are intentionally OPEN records.
|
|
6
|
+
*
|
|
7
|
+
* `io: 'input'` is LOAD-BEARING: `z.toJSONSchema` defaults to `io: 'output'`, which marks every
|
|
8
|
+
* `.default()`ed field REQUIRED — making the exported artifact REJECT a default-omitting minimal
|
|
9
|
+
* spec (`{version, metadata}`) that `parseSpec` ACCEPTS (the parser fills the defaults). `io:'input'`
|
|
10
|
+
* exports the PRE-default INPUT shape, so the artifact validates exactly what an author may WRITE —
|
|
11
|
+
* matching the parser's accept set (the round-trip contract test drives a default-omitting object
|
|
12
|
+
* through the artifact to lock this).
|
|
13
|
+
*
|
|
14
|
+
* `unrepresentable` defaults to `'throw'`; the grammar uses only representable types
|
|
15
|
+
* (string/literal/enum/array/record/object/boolean/number/discriminatedUnion), all verified to
|
|
16
|
+
* export, so this never throws for the current grammar.
|
|
17
|
+
*/
|
|
18
|
+
export declare function exportJsonSchema(): Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Export the `ProductSpec` (Product-YAML) grammar as a JSON-Schema (draft-2020-12) — the SAME
|
|
21
|
+
* `io:'input'` discipline as `exportJsonSchema` (so a default-omitting minimal product doc validates
|
|
22
|
+
* against the artifact exactly as `parseProductSpec` accepts it). The Product-YAML `contracts` slot is
|
|
23
|
+
* an intentionally OPEN record (its closed vocabulary is enforced by product-lint, not this artifact),
|
|
24
|
+
* mirroring how the RaySpec artifact leaves a tool's `parameters` open. This artifact is kept
|
|
25
|
+
* fresh by the `gate:spec-schema` drift gate alongside `spec.schema.json` (`packages/spec/product.schema.json`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function exportProductJsonSchema(): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Export the ONE canonical schema for the `version:'1.0'` document. A
|
|
30
|
+
* `version:'1.0'` doc is one of two PROFILES, discriminated by the presence of the `product:` section:
|
|
31
|
+
* the BACKEND profile (`RaySpec` — the low-level surface AND the internal engine target) or the
|
|
32
|
+
* PRODUCT profile (`ProductSpec` — the high-level product-meaning surface). This is a hand-composed
|
|
33
|
+
* `oneOf` over the two existing profile schemas (a `oneOf` with a `product` required/forbidden
|
|
34
|
+
* discriminant is clearer + diff-reviewable than `z.toJSONSchema` over a top-level union of two strict
|
|
35
|
+
* objects). It is UNAMBIGUOUS by construction: the backend arm keeps `additionalProperties:false`, so a
|
|
36
|
+
* `product`-carrying doc CANNOT match it, and the product arm REQUIRES `product` — so exactly one arm
|
|
37
|
+
* matches. Both arms stay draft-2020-12 + `io:'input'` (defaults-applied-on-parse) and fail-closed
|
|
38
|
+
* (every strict level keeps `additionalProperties:false`). The dialect `$schema` declaration lives ONLY
|
|
39
|
+
* at the unified root; the inner profile `$schema` keys are stripped so the artifact is one well-formed
|
|
40
|
+
* 2020-12 document. Kept fresh by `gate:spec-schema` alongside the two profile views.
|
|
41
|
+
*
|
|
42
|
+
* `$defs` HOISTING (the BLOCKER fix): `z.toJSONSchema` factors a reused sub-schema into a ROOT-scoped
|
|
43
|
+
* `$defs.__schemaN` with document-root-relative `"$ref": "#/$defs/__schemaN"` pointers (the product
|
|
44
|
+
* profile emits one such def today; the backend profile emits none). A JSON-Pointer `$ref` is ALWAYS
|
|
45
|
+
* resolved from the DOCUMENT ROOT (`#`), NOT from the `oneOf[n]` object it sits under — so leaving a
|
|
46
|
+
* profile's `$defs` NESTED under an arm leaves its refs dangling (Ajv2020 raises `can't resolve
|
|
47
|
+
* reference #/$defs/__schema0 from id #`). This exporter therefore HOISTS every arm's `$defs` up to the
|
|
48
|
+
* unified ROOT (deleting it from the arm), so the inner `#/$defs/...` refs resolve against the
|
|
49
|
+
* now-present root `$defs`. A cross-arm `$defs` key collision throws (fail loud, never silently clobber).
|
|
50
|
+
* The `packages/spec/src/export.test.ts` Ajv2020 compile test is the forcing-function that keeps this
|
|
51
|
+
* honest (a regression to nested `$defs` turns it red with a MissingRefError).
|
|
52
|
+
*/
|
|
53
|
+
export declare function exportUnifiedJsonSchema(): Record<string, unknown>;
|
|
54
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAK1D;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwCjE"}
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-Schema exporter for the RaySpec config grammar.
|
|
3
|
+
*
|
|
4
|
+
* The design calls for "Zod + JSON-Schema export". The pinned `zod@4.4.3` ships a native
|
|
5
|
+
* `z.toJSONSchema` (verified doc-first) so this needs NO new dependency. We export at the
|
|
6
|
+
* draft-2020-12 dialect — the SAME dialect `packages/platform/src/dispatch.ts` compiles tool
|
|
7
|
+
* schemas with (`ajv/dist/2020`) — so the runtime exporter and the runtime validator speak one
|
|
8
|
+
* JSON-Schema language.
|
|
9
|
+
*
|
|
10
|
+
* Scope: build the RUNTIME exporter + the round-trip contract test.
|
|
11
|
+
*
|
|
12
|
+
* The MINIMAL schema-emit + drift-gate: this exporter is now ALSO the source for a CHECKED-IN
|
|
13
|
+
* `packages/spec/spec.schema.json` artifact, kept fresh by the structural CI gate `gate:spec-schema`
|
|
14
|
+
* (`scripts/check-spec-schema.mjs` — re-derives the schema, serializes it through one shared helper, and
|
|
15
|
+
* byte-compares to the committed file; regenerate with `--write`). The gate only checks FRESHNESS — this
|
|
16
|
+
* function stays the single source of truth. Generated handler TYPES remain deferred.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { RaySpec } from './grammar.js';
|
|
20
|
+
import { ProductSpec } from './product-grammar.js';
|
|
21
|
+
/**
|
|
22
|
+
* Export the `RaySpec` grammar as a JSON-Schema (draft-2020-12) object. Every STRICT grammar
|
|
23
|
+
* object level carries `additionalProperties:false` (the fail-closed shape an Ajv2020 instance
|
|
24
|
+
* enforces byte-for-byte); the three embedded JSON-Schema slots (tool `parameters`/`outputSchema`,
|
|
25
|
+
* agent `outputSchema.schema`) are intentionally OPEN records.
|
|
26
|
+
*
|
|
27
|
+
* `io: 'input'` is LOAD-BEARING: `z.toJSONSchema` defaults to `io: 'output'`, which marks every
|
|
28
|
+
* `.default()`ed field REQUIRED — making the exported artifact REJECT a default-omitting minimal
|
|
29
|
+
* spec (`{version, metadata}`) that `parseSpec` ACCEPTS (the parser fills the defaults). `io:'input'`
|
|
30
|
+
* exports the PRE-default INPUT shape, so the artifact validates exactly what an author may WRITE —
|
|
31
|
+
* matching the parser's accept set (the round-trip contract test drives a default-omitting object
|
|
32
|
+
* through the artifact to lock this).
|
|
33
|
+
*
|
|
34
|
+
* `unrepresentable` defaults to `'throw'`; the grammar uses only representable types
|
|
35
|
+
* (string/literal/enum/array/record/object/boolean/number/discriminatedUnion), all verified to
|
|
36
|
+
* export, so this never throws for the current grammar.
|
|
37
|
+
*/
|
|
38
|
+
export function exportJsonSchema() {
|
|
39
|
+
return z.toJSONSchema(RaySpec, {
|
|
40
|
+
target: 'draft-2020-12',
|
|
41
|
+
io: 'input',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Export the `ProductSpec` (Product-YAML) grammar as a JSON-Schema (draft-2020-12) — the SAME
|
|
46
|
+
* `io:'input'` discipline as `exportJsonSchema` (so a default-omitting minimal product doc validates
|
|
47
|
+
* against the artifact exactly as `parseProductSpec` accepts it). The Product-YAML `contracts` slot is
|
|
48
|
+
* an intentionally OPEN record (its closed vocabulary is enforced by product-lint, not this artifact),
|
|
49
|
+
* mirroring how the RaySpec artifact leaves a tool's `parameters` open. This artifact is kept
|
|
50
|
+
* fresh by the `gate:spec-schema` drift gate alongside `spec.schema.json` (`packages/spec/product.schema.json`).
|
|
51
|
+
*/
|
|
52
|
+
export function exportProductJsonSchema() {
|
|
53
|
+
return z.toJSONSchema(ProductSpec, {
|
|
54
|
+
target: 'draft-2020-12',
|
|
55
|
+
io: 'input',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Export the ONE canonical schema for the `version:'1.0'` document. A
|
|
60
|
+
* `version:'1.0'` doc is one of two PROFILES, discriminated by the presence of the `product:` section:
|
|
61
|
+
* the BACKEND profile (`RaySpec` — the low-level surface AND the internal engine target) or the
|
|
62
|
+
* PRODUCT profile (`ProductSpec` — the high-level product-meaning surface). This is a hand-composed
|
|
63
|
+
* `oneOf` over the two existing profile schemas (a `oneOf` with a `product` required/forbidden
|
|
64
|
+
* discriminant is clearer + diff-reviewable than `z.toJSONSchema` over a top-level union of two strict
|
|
65
|
+
* objects). It is UNAMBIGUOUS by construction: the backend arm keeps `additionalProperties:false`, so a
|
|
66
|
+
* `product`-carrying doc CANNOT match it, and the product arm REQUIRES `product` — so exactly one arm
|
|
67
|
+
* matches. Both arms stay draft-2020-12 + `io:'input'` (defaults-applied-on-parse) and fail-closed
|
|
68
|
+
* (every strict level keeps `additionalProperties:false`). The dialect `$schema` declaration lives ONLY
|
|
69
|
+
* at the unified root; the inner profile `$schema` keys are stripped so the artifact is one well-formed
|
|
70
|
+
* 2020-12 document. Kept fresh by `gate:spec-schema` alongside the two profile views.
|
|
71
|
+
*
|
|
72
|
+
* `$defs` HOISTING (the BLOCKER fix): `z.toJSONSchema` factors a reused sub-schema into a ROOT-scoped
|
|
73
|
+
* `$defs.__schemaN` with document-root-relative `"$ref": "#/$defs/__schemaN"` pointers (the product
|
|
74
|
+
* profile emits one such def today; the backend profile emits none). A JSON-Pointer `$ref` is ALWAYS
|
|
75
|
+
* resolved from the DOCUMENT ROOT (`#`), NOT from the `oneOf[n]` object it sits under — so leaving a
|
|
76
|
+
* profile's `$defs` NESTED under an arm leaves its refs dangling (Ajv2020 raises `can't resolve
|
|
77
|
+
* reference #/$defs/__schema0 from id #`). This exporter therefore HOISTS every arm's `$defs` up to the
|
|
78
|
+
* unified ROOT (deleting it from the arm), so the inner `#/$defs/...` refs resolve against the
|
|
79
|
+
* now-present root `$defs`. A cross-arm `$defs` key collision throws (fail loud, never silently clobber).
|
|
80
|
+
* The `packages/spec/src/export.test.ts` Ajv2020 compile test is the forcing-function that keeps this
|
|
81
|
+
* honest (a regression to nested `$defs` turns it red with a MissingRefError).
|
|
82
|
+
*/
|
|
83
|
+
export function exportUnifiedJsonSchema() {
|
|
84
|
+
const strip$schema = (s) => {
|
|
85
|
+
const { $schema: _dialect, ...rest } = s;
|
|
86
|
+
return rest;
|
|
87
|
+
};
|
|
88
|
+
// The two profile arms, each stripped of its inner dialect `$schema` (the unified root carries it).
|
|
89
|
+
const arms = [strip$schema(exportJsonSchema()), strip$schema(exportProductJsonSchema())];
|
|
90
|
+
// Hoist each arm's ROOT-scoped `$defs` up to the unified root, deleting it from the arm (see the
|
|
91
|
+
// `$defs` HOISTING note above). A JSON-Pointer `$ref` resolves from the DOCUMENT ROOT, NOT the
|
|
92
|
+
// `oneOf[n]` object it sits under, so a profile's `#/$defs/...` refs would DANGLE if its `$defs`
|
|
93
|
+
// stayed nested. Merge under one root `$defs`; a cross-arm key collision throws (fail loud).
|
|
94
|
+
const rootDefs = {};
|
|
95
|
+
for (const arm of arms) {
|
|
96
|
+
const armDefs = arm.$defs;
|
|
97
|
+
if (!armDefs)
|
|
98
|
+
continue;
|
|
99
|
+
for (const [key, def] of Object.entries(armDefs)) {
|
|
100
|
+
if (key in rootDefs) {
|
|
101
|
+
throw new Error(`exportUnifiedJsonSchema: $defs key collision '${key}' across the backend/product profiles — ` +
|
|
102
|
+
'namespace the arm defs before hoisting (both arms cannot share a root-scoped def name).');
|
|
103
|
+
}
|
|
104
|
+
rootDefs[key] = def;
|
|
105
|
+
}
|
|
106
|
+
delete arm.$defs;
|
|
107
|
+
}
|
|
108
|
+
const unified = {
|
|
109
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
110
|
+
title: 'RaySpec spec (version 1.0) — one language, two profiles (backend | product)',
|
|
111
|
+
description: "A version:'1.0' document is one of two profiles, discriminated by the presence of the " +
|
|
112
|
+
'`product:` section: the backend profile (low-level RaySpec, the internal engine target) ' +
|
|
113
|
+
'or the product profile (high-level product meaning, lowered to the backend profile at deploy).',
|
|
114
|
+
oneOf: arms,
|
|
115
|
+
};
|
|
116
|
+
// Only emit `$defs` if any arm contributed one (keeps the artifact minimal when no arm factors a def).
|
|
117
|
+
if (Object.keys(rootDefs).length > 0)
|
|
118
|
+
unified.$defs = rootDefs;
|
|
119
|
+
return unified;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE;QAC7B,MAAM,EAAE,eAAe;QACvB,EAAE,EAAE,OAAO;KACZ,CAA4B,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,CAAC,CAAC,YAAY,CAAC,WAAW,EAAE;QACjC,MAAM,EAAE,eAAe;QACvB,EAAE,EAAE,OAAO;KACZ,CAA4B,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,YAAY,GAAG,CAAC,CAA0B,EAA2B,EAAE;QAC3E,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,oGAAoG;IACpG,MAAM,IAAI,GAAG,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC,EAAE,YAAY,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC;IAEzF,iGAAiG;IACjG,+FAA+F;IAC/F,iGAAiG;IACjG,6FAA6F;IAC7F,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,OAAO,GAAI,GAA2C,CAAC,KAAK,CAAC;QACnE,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACjD,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CACb,iDAAiD,GAAG,0CAA0C;oBAC5F,yFAAyF,CAC5F,CAAC;YACJ,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACtB,CAAC;QACD,OAAQ,GAA2B,CAAC,KAAK,CAAC;IAC5C,CAAC;IAED,MAAM,OAAO,GAA4B;QACvC,OAAO,EAAE,8CAA8C;QACvD,KAAK,EAAE,6EAA6E;QACpF,WAAW,EACT,wFAAwF;YACxF,0FAA0F;YAC1F,gGAAgG;QAClG,KAAK,EAAE,IAAI;KACZ,CAAC;IACF,uGAAuG;IACvG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC;IAC/D,OAAO,OAAO,CAAC;AACjB,CAAC"}
|