@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.
Files changed (62) hide show
  1. package/LICENSE +105 -0
  2. package/dist/brace-params.d.ts +32 -0
  3. package/dist/brace-params.d.ts.map +1 -0
  4. package/dist/brace-params.js +89 -0
  5. package/dist/brace-params.js.map +1 -0
  6. package/dist/detect.d.ts +44 -0
  7. package/dist/detect.d.ts.map +1 -0
  8. package/dist/detect.js +72 -0
  9. package/dist/detect.js.map +1 -0
  10. package/dist/errors.d.ts +204 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +165 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/export.d.ts +54 -0
  15. package/dist/export.d.ts.map +1 -0
  16. package/dist/export.js +121 -0
  17. package/dist/export.js.map +1 -0
  18. package/dist/grammar.d.ts +569 -0
  19. package/dist/grammar.d.ts.map +1 -0
  20. package/dist/grammar.js +503 -0
  21. package/dist/grammar.js.map +1 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +26 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/lint.d.ts +79 -0
  27. package/dist/lint.d.ts.map +1 -0
  28. package/dist/lint.js +823 -0
  29. package/dist/lint.js.map +1 -0
  30. package/dist/parse.d.ts +8 -0
  31. package/dist/parse.d.ts.map +1 -0
  32. package/dist/parse.js +118 -0
  33. package/dist/parse.js.map +1 -0
  34. package/dist/product-events.d.ts +73 -0
  35. package/dist/product-events.d.ts.map +1 -0
  36. package/dist/product-events.js +42 -0
  37. package/dist/product-events.js.map +1 -0
  38. package/dist/product-grammar.d.ts +948 -0
  39. package/dist/product-grammar.d.ts.map +1 -0
  40. package/dist/product-grammar.js +581 -0
  41. package/dist/product-grammar.js.map +1 -0
  42. package/dist/product-lint.d.ts +74 -0
  43. package/dist/product-lint.d.ts.map +1 -0
  44. package/dist/product-lint.js +821 -0
  45. package/dist/product-lint.js.map +1 -0
  46. package/dist/product-parse.d.ts +8 -0
  47. package/dist/product-parse.d.ts.map +1 -0
  48. package/dist/product-parse.js +119 -0
  49. package/dist/product-parse.js.map +1 -0
  50. package/dist/product-scope.d.ts +53 -0
  51. package/dist/product-scope.d.ts.map +1 -0
  52. package/dist/product-scope.js +55 -0
  53. package/dist/product-scope.js.map +1 -0
  54. package/dist/product-views-lint.d.ts +14 -0
  55. package/dist/product-views-lint.d.ts.map +1 -0
  56. package/dist/product-views-lint.js +669 -0
  57. package/dist/product-views-lint.js.map +1 -0
  58. package/dist/product-views.d.ts +426 -0
  59. package/dist/product-views.d.ts.map +1 -0
  60. package/dist/product-views.js +317 -0
  61. package/dist/product-views.js.map +1 -0
  62. package/package.json +34 -0
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Socialinsiders UG (haftungsbeschränkt)
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `{param}` extraction/rewriting for declared route paths — a single forward character scan, with NO
3
+ * regular expression, so the work is strictly linear in the input length for EVERY input (no
4
+ * backtracking, no quantifier, and no length cap on the param name).
5
+ *
6
+ * The two helpers are the shared, byte-exact equivalent of iterating the pattern `\{([^}/]+)\}`
7
+ * globally over a path — the form the route/view emitters and the Hono-path rewrite historically used.
8
+ * They preserve that pattern's EXACT semantics for every input, including its edges:
9
+ * - `{}` → NOT a param (the run needs at least one character);
10
+ * - `{a/b}` → NOT a param (a `/` ends the run before a `}` can close it);
11
+ * - `{a}{b}` → two params (`a`, `b`);
12
+ * - `{a{b}` → one param whose name is `a{b` (an inner `{` is an ordinary name character);
13
+ * - `{unclosed` → no param (no closing `}`);
14
+ * - names of ANY length (1, 128, 129, thousands) and ANY code points (ASCII, emoji, astral).
15
+ *
16
+ * A `{param}` here is `{`, then a maximal run of ≥1 characters that are neither `}` nor `/`, then a
17
+ * `}`. Because the run stops at the first `}` or `/`, a match exists at a `{` iff that first stopping
18
+ * character is a `}` (a `/` or end-of-input means no match at this `{`); on a non-match the scan
19
+ * advances by a single character, exactly as a global regex retries at the next position.
20
+ */
21
+ /**
22
+ * Extract the `{param}` names from a declared route path in order (e.g. `/meetings/{id}/x` → `['id']`).
23
+ * Byte-exact equivalent of collecting capture group 1 of a global `\{([^}/]+)\}` scan.
24
+ */
25
+ export declare function braceParamNames(path: string): string[];
26
+ /**
27
+ * Rewrite each `{param}` in a declared route path via `replace(name)`, copying every other character
28
+ * verbatim (e.g. `toHonoPath` uses `(name) => ':' + name`; the operationId slug uses
29
+ * `(name) => '_by_' + name + '_'`). Byte-exact equivalent of `path.replace(/\{([^}/]+)\}/g, cb)`.
30
+ */
31
+ export declare function rewriteBraceParams(path: string, replace: (name: string) => string): string;
32
+ //# sourceMappingURL=brace-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brace-params.d.ts","sourceRoot":"","sources":["../src/brace-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmBH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAqB1F"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `{param}` extraction/rewriting for declared route paths — a single forward character scan, with NO
3
+ * regular expression, so the work is strictly linear in the input length for EVERY input (no
4
+ * backtracking, no quantifier, and no length cap on the param name).
5
+ *
6
+ * The two helpers are the shared, byte-exact equivalent of iterating the pattern `\{([^}/]+)\}`
7
+ * globally over a path — the form the route/view emitters and the Hono-path rewrite historically used.
8
+ * They preserve that pattern's EXACT semantics for every input, including its edges:
9
+ * - `{}` → NOT a param (the run needs at least one character);
10
+ * - `{a/b}` → NOT a param (a `/` ends the run before a `}` can close it);
11
+ * - `{a}{b}` → two params (`a`, `b`);
12
+ * - `{a{b}` → one param whose name is `a{b` (an inner `{` is an ordinary name character);
13
+ * - `{unclosed` → no param (no closing `}`);
14
+ * - names of ANY length (1, 128, 129, thousands) and ANY code points (ASCII, emoji, astral).
15
+ *
16
+ * A `{param}` here is `{`, then a maximal run of ≥1 characters that are neither `}` nor `/`, then a
17
+ * `}`. Because the run stops at the first `}` or `/`, a match exists at a `{` iff that first stopping
18
+ * character is a `}` (a `/` or end-of-input means no match at this `{`); on a non-match the scan
19
+ * advances by a single character, exactly as a global regex retries at the next position.
20
+ */
21
+ /**
22
+ * Locate the `{param}` span starting at `open` (where `path[open] === '{'`). Returns the closing-brace
23
+ * index and the param name when a valid `{name}` (name length ≥ 1, first stopping char is `}`) begins
24
+ * there, or `undefined` when this `{` does not open a param. Shared by both public helpers so the
25
+ * extract and rewrite paths can never drift.
26
+ */
27
+ function braceSpanAt(path, open) {
28
+ // Walk to the first `}` or `/` after the `{` (or the end of the string).
29
+ let j = open + 1;
30
+ while (j < path.length && path[j] !== '}' && path[j] !== '/')
31
+ j++;
32
+ // A param exists iff that stopping char is a `}` AND at least one name char preceded it.
33
+ if (j < path.length && path[j] === '}' && j > open + 1) {
34
+ return { close: j, name: path.slice(open + 1, j) };
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * Extract the `{param}` names from a declared route path in order (e.g. `/meetings/{id}/x` → `['id']`).
40
+ * Byte-exact equivalent of collecting capture group 1 of a global `\{([^}/]+)\}` scan.
41
+ */
42
+ export function braceParamNames(path) {
43
+ const out = [];
44
+ let i = 0;
45
+ while (i < path.length) {
46
+ if (path[i] !== '{') {
47
+ i++;
48
+ continue;
49
+ }
50
+ const span = braceSpanAt(path, i);
51
+ if (span) {
52
+ out.push(span.name);
53
+ i = span.close + 1; // continue after the closing `}`
54
+ }
55
+ else {
56
+ i++; // not a param — advance one char and retry (a global regex does the same)
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ /**
62
+ * Rewrite each `{param}` in a declared route path via `replace(name)`, copying every other character
63
+ * verbatim (e.g. `toHonoPath` uses `(name) => ':' + name`; the operationId slug uses
64
+ * `(name) => '_by_' + name + '_'`). Byte-exact equivalent of `path.replace(/\{([^}/]+)\}/g, cb)`.
65
+ */
66
+ export function rewriteBraceParams(path, replace) {
67
+ let out = '';
68
+ let last = 0; // start of the not-yet-copied verbatim region
69
+ let i = 0;
70
+ while (i < path.length) {
71
+ if (path[i] !== '{') {
72
+ i++;
73
+ continue;
74
+ }
75
+ const span = braceSpanAt(path, i);
76
+ if (span) {
77
+ out += path.slice(last, i); // verbatim text before this `{param}`
78
+ out += replace(span.name);
79
+ i = span.close + 1;
80
+ last = i;
81
+ }
82
+ else {
83
+ i++;
84
+ }
85
+ }
86
+ out += path.slice(last); // trailing verbatim text
87
+ return out;
88
+ }
89
+ //# sourceMappingURL=brace-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brace-params.js","sourceRoot":"","sources":["../src/brace-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;GAKG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,yEAAyE;IACzE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IAClE,yFAAyF;IACzF,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACpB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAClC,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,iCAAiC;QACvD,CAAC;aAAM,CAAC;YACN,CAAC,EAAE,CAAC,CAAC,0EAA0E;QACjF,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,OAAiC;IAChF,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,8CAA8C;IAC5D,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACpB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAClC,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,sCAAsC;YAClE,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YACnB,IAAI,GAAG,CAAC,CAAC;QACX,CAAC;aAAM,CAAC;YACN,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IACD,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,yBAAyB;IAClD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { SpecError } from './errors.js';
2
+ import { type RaySpec } from './grammar.js';
3
+ import type { ProductSpec } from './product-grammar.js';
4
+ /** Which document PROFILE a raw YAML string is. `unknown` = not parseable to an object root. */
5
+ export type SpecKind = 'rayspec' | 'product' | 'unknown';
6
+ /**
7
+ * Detect the document profile. A `version:'1.0'` doc is the PRODUCT profile iff it carries a top-level
8
+ * `product:` section, else the BACKEND profile. Any other object root (a version-less doc, or one with
9
+ * an unsupported version) → the backend parser, which fails closed there with a clean
10
+ * `unsupported_version`. Fail-closed: a non-object / unparseable root → `unknown`.
11
+ */
12
+ export declare function detectSpecKind(rawYamlText: string): SpecKind;
13
+ /** A validate-only envelope for callers (doctor/plan/deploy) that only need the fail-closed verdict. */
14
+ export interface ValidateResult {
15
+ readonly ok: boolean;
16
+ readonly kind: SpecKind;
17
+ readonly errors: SpecError[];
18
+ }
19
+ /**
20
+ * Validate a raw spec of EITHER family, collapsing to `{ ok, kind, errors }`. Routes to the correct
21
+ * parser by `detectSpecKind`. An `unknown` root is routed to `parseSpec` so its existing
22
+ * `unsupported_version`/`yaml_parse_error` message is preserved (no behavior change for RaySpec).
23
+ */
24
+ export declare function validateAnySpec(rawYamlText: string): ValidateResult;
25
+ /**
26
+ * A discriminated parse result carrying the typed value of whichever family the doc is. Callers that
27
+ * need the parsed value (not just the verdict) use this. `unknown`/`rayspec` roots parse as RaySpec.
28
+ */
29
+ export type AnySpecParse = {
30
+ readonly ok: true;
31
+ readonly kind: 'rayspec';
32
+ readonly spec: RaySpec;
33
+ } | {
34
+ readonly ok: true;
35
+ readonly kind: 'product';
36
+ readonly spec: ProductSpec;
37
+ } | {
38
+ readonly ok: false;
39
+ readonly kind: SpecKind;
40
+ readonly errors: SpecError[];
41
+ };
42
+ /** Parse a raw spec of either family, returning the typed value on success. */
43
+ export declare function parseAnySpec(rawYamlText: string): AnySpecParse;
44
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,gGAAgG;AAChG,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAY5D;AAED,wGAAwG;AACxG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAUnE;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC3E;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAElF,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAY9D"}
package/dist/detect.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Document-kind detection + a unified validator.
3
+ *
4
+ * RaySpec is ONE declarative language — `version: '1.0'` — with two PROFILES, told apart by the
5
+ * presence of the `product:` archetype discriminant:
6
+ * • RaySpec (backend profile) — a `version:'1.0'` doc with NO `product:` section (grammar.ts /
7
+ * parse.ts) — the low-level surface AND the internal engine target.
8
+ * • Product-YAML (product profile) — a `version:'1.0'` doc that carries the required `product:`
9
+ * section (product-grammar.ts / product-parse.ts) — the
10
+ * high-level product-meaning surface that lowers to the backend.
11
+ *
12
+ * `detectSpecKind` keys on `version:'1.0'` + `product:` presence: a doc that carries a top-level
13
+ * `product:` section is the product profile, else the backend profile. A YAML parse/shape failure is
14
+ * reported as `unknown` so the caller can surface a clean error.
15
+ */
16
+ import { parse as parseYaml } from 'yaml';
17
+ import { SPEC_VERSION } from './grammar.js';
18
+ import { parseSpec } from './parse.js';
19
+ import { parseProductSpec } from './product-parse.js';
20
+ /**
21
+ * Detect the document profile. A `version:'1.0'` doc is the PRODUCT profile iff it carries a top-level
22
+ * `product:` section, else the BACKEND profile. Any other object root (a version-less doc, or one with
23
+ * an unsupported version) → the backend parser, which fails closed there with a clean
24
+ * `unsupported_version`. Fail-closed: a non-object / unparseable root → `unknown`.
25
+ */
26
+ export function detectSpecKind(rawYamlText) {
27
+ let loaded;
28
+ try {
29
+ loaded = parseYaml(rawYamlText);
30
+ }
31
+ catch {
32
+ return 'unknown';
33
+ }
34
+ if (loaded === null || typeof loaded !== 'object' || Array.isArray(loaded))
35
+ return 'unknown';
36
+ const doc = loaded;
37
+ // version:'1.0' + the `product:` discriminant selects the product profile; everything else is backend.
38
+ if (doc.version === SPEC_VERSION && 'product' in doc)
39
+ return 'product';
40
+ return 'rayspec';
41
+ }
42
+ /**
43
+ * Validate a raw spec of EITHER family, collapsing to `{ ok, kind, errors }`. Routes to the correct
44
+ * parser by `detectSpecKind`. An `unknown` root is routed to `parseSpec` so its existing
45
+ * `unsupported_version`/`yaml_parse_error` message is preserved (no behavior change for RaySpec).
46
+ */
47
+ export function validateAnySpec(rawYamlText) {
48
+ const kind = detectSpecKind(rawYamlText);
49
+ if (kind === 'product') {
50
+ const res = parseProductSpec(rawYamlText);
51
+ return res.ok ? { ok: true, kind, errors: [] } : { ok: false, kind, errors: res.errors };
52
+ }
53
+ const res = parseSpec(rawYamlText);
54
+ return res.ok
55
+ ? { ok: true, kind: 'rayspec', errors: [] }
56
+ : { ok: false, kind: 'rayspec', errors: res.errors };
57
+ }
58
+ /** Parse a raw spec of either family, returning the typed value on success. */
59
+ export function parseAnySpec(rawYamlText) {
60
+ const kind = detectSpecKind(rawYamlText);
61
+ if (kind === 'product') {
62
+ const res = parseProductSpec(rawYamlText);
63
+ return res.ok
64
+ ? { ok: true, kind: 'product', spec: res.value }
65
+ : { ok: false, kind: 'product', errors: res.errors };
66
+ }
67
+ const res = parseSpec(rawYamlText);
68
+ return res.ok
69
+ ? { ok: true, kind: 'rayspec', spec: res.value }
70
+ : { ok: false, kind: 'rayspec', errors: res.errors };
71
+ }
72
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAgB,YAAY,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKtD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB;IAChD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7F,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,uGAAuG;IACvG,IAAI,GAAG,CAAC,OAAO,KAAK,YAAY,IAAI,SAAS,IAAI,GAAG;QAAE,OAAO,SAAS,CAAC;IACvE,OAAO,SAAS,CAAC;AACnB,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,MAAM,IAAI,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC1C,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC3F,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IACnC,OAAO,GAAG,CAAC,EAAE;QACX,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;QAC3C,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;AACzD,CAAC;AAWD,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,IAAI,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,GAAG,GAAmC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC1E,OAAO,GAAG,CAAC,EAAE;YACX,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE;YAChD,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,GAAG,GAA+B,SAAS,CAAC,WAAW,CAAC,CAAC;IAC/D,OAAO,GAAG,CAAC,EAAE;QACX,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE;QAChD,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1,204 @@
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 declare const SpecErrorCode: z.ZodEnum<{
100
+ yaml_parse_error: "yaml_parse_error";
101
+ unsupported_version: "unsupported_version";
102
+ schema_violation: "schema_violation";
103
+ unknown_field: "unknown_field";
104
+ dangling_ref: "dangling_ref";
105
+ duplicate_name: "duplicate_name";
106
+ capability_violation: "capability_violation";
107
+ invalid_embedded_schema: "invalid_embedded_schema";
108
+ reserved_column_name: "reserved_column_name";
109
+ reserved_query_keyword: "reserved_query_keyword";
110
+ frontend_route_collision: "frontend_route_collision";
111
+ frontend_dir_missing: "frontend_dir_missing";
112
+ fk_cycle: "fk_cycle";
113
+ no_code_in_yaml: "no_code_in_yaml";
114
+ provider_native_leak: "provider_native_leak";
115
+ invalid_capability_status: "invalid_capability_status";
116
+ invalid_contract: "invalid_contract";
117
+ prompt_execution_claim: "prompt_execution_claim";
118
+ production_execution_claim: "production_execution_claim";
119
+ invalid_dependency_order: "invalid_dependency_order";
120
+ invalid_view: "invalid_view";
121
+ invalid_store: "invalid_store";
122
+ }>;
123
+ export type SpecErrorCode = z.infer<typeof SpecErrorCode>;
124
+ /** A single fail-closed spec violation (closed code + message + optional JSON path). */
125
+ export declare const SpecError: z.ZodObject<{
126
+ code: z.ZodEnum<{
127
+ yaml_parse_error: "yaml_parse_error";
128
+ unsupported_version: "unsupported_version";
129
+ schema_violation: "schema_violation";
130
+ unknown_field: "unknown_field";
131
+ dangling_ref: "dangling_ref";
132
+ duplicate_name: "duplicate_name";
133
+ capability_violation: "capability_violation";
134
+ invalid_embedded_schema: "invalid_embedded_schema";
135
+ reserved_column_name: "reserved_column_name";
136
+ reserved_query_keyword: "reserved_query_keyword";
137
+ frontend_route_collision: "frontend_route_collision";
138
+ frontend_dir_missing: "frontend_dir_missing";
139
+ fk_cycle: "fk_cycle";
140
+ no_code_in_yaml: "no_code_in_yaml";
141
+ provider_native_leak: "provider_native_leak";
142
+ invalid_capability_status: "invalid_capability_status";
143
+ invalid_contract: "invalid_contract";
144
+ prompt_execution_claim: "prompt_execution_claim";
145
+ production_execution_claim: "production_execution_claim";
146
+ invalid_dependency_order: "invalid_dependency_order";
147
+ invalid_view: "invalid_view";
148
+ invalid_store: "invalid_store";
149
+ }>;
150
+ message: z.ZodString;
151
+ path: z.ZodOptional<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ export type SpecError = z.infer<typeof SpecError>;
154
+ /** Construct a SpecError (path omitted when undefined so the envelope stays minimal). */
155
+ export declare function specError(code: SpecErrorCode, message: string, path?: string): SpecError;
156
+ /**
157
+ * The CLOSED set of NON-FATAL spec-warning codes. A warning flags a documented, deliberately-permitted
158
+ * interaction the author should be AWARE of — it does NOT fail `doctor`/`plan` (unlike a `SpecError`).
159
+ * Kept a distinct closed vocabulary from `SpecErrorCode` so a fresh session never confuses "advisory"
160
+ * with "fail-closed".
161
+ *
162
+ * - `softdelete_fk_restrict` — a `softDelete` store is the TARGET of a `restrict` business-key
163
+ * (`referencesColumn`) foreign key. Soft-deleting such a parent is an
164
+ * `UPDATE(deleted_at)` that does NOT fire the database ON DELETE restrict,
165
+ * so children keep pointing at the tombstoned row — the restrict guarantee
166
+ * only binds on a HARD delete. This is a permitted, documented interaction.
167
+ * - `fk_forward_reference` — a store declares a foreign key onto another store declared LATER in the
168
+ * `stores` array. The product-SQL generator topo-sorts stores so the parent
169
+ * table is created before the child's FK is added, so a forward reference
170
+ * still applies cleanly; this advisory notes only that the declared order
171
+ * relies on that reordering (declaring the parent first makes it explicit).
172
+ * Acyclic by construction — a true cycle is the fail-closed `fk_cycle`
173
+ * error, never a warning.
174
+ */
175
+ export declare const SpecWarningCode: z.ZodEnum<{
176
+ softdelete_fk_restrict: "softdelete_fk_restrict";
177
+ fk_forward_reference: "fk_forward_reference";
178
+ }>;
179
+ export type SpecWarningCode = z.infer<typeof SpecWarningCode>;
180
+ /** A single NON-FATAL spec warning (closed code + message + optional JSON path). Never fails a parse. */
181
+ export declare const SpecWarning: z.ZodObject<{
182
+ code: z.ZodEnum<{
183
+ softdelete_fk_restrict: "softdelete_fk_restrict";
184
+ fk_forward_reference: "fk_forward_reference";
185
+ }>;
186
+ message: z.ZodString;
187
+ path: z.ZodOptional<z.ZodString>;
188
+ }, z.core.$strip>;
189
+ export type SpecWarning = z.infer<typeof SpecWarning>;
190
+ /** Construct a SpecWarning (path omitted when undefined so the envelope stays minimal). */
191
+ export declare function specWarning(code: SpecWarningCode, message: string, path?: string): SpecWarning;
192
+ /**
193
+ * The result of `parseSpec` — a discriminated `Result` so a caller MUST check `ok` before
194
+ * touching `value` (the fail-closed contract: a spec with any violation yields `ok:false` and
195
+ * the full violation list, never a partially-trusted value).
196
+ */
197
+ export type Result<T, E> = {
198
+ ok: true;
199
+ value: T;
200
+ } | {
201
+ ok: false;
202
+ errors: E[];
203
+ };
204
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;EAuBxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,wFAAwF;AACxF,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAElD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAExF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe;;;EAA6D,CAAC;AAC1F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,yGAAyG;AACzG,eAAO,MAAM,WAAW;;;;;;;iBAKtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAE9F;AAED;;;;GAIG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,CAAC,EAAE,CAAA;CAAE,CAAC"}