@telorun/analyzer 0.62.0 → 0.63.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/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +6 -0
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +343 -13
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +2 -7
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/import-resolution-diagnostics.d.ts +9 -2
- package/dist/import-resolution-diagnostics.d.ts.map +1 -1
- package/dist/import-resolution-diagnostics.js +26 -7
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/invocation-contract.d.ts +6 -1
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +27 -4
- package/dist/loaded-types.d.ts +12 -3
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +0 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +62 -41
- package/dist/manifest-schemas.d.ts +699 -0
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +306 -14
- package/dist/module-compatibility.d.ts +40 -0
- package/dist/module-compatibility.d.ts.map +1 -0
- package/dist/module-compatibility.js +52 -0
- package/dist/ref-slot-reading.d.ts +30 -0
- package/dist/ref-slot-reading.d.ts.map +1 -0
- package/dist/ref-slot-reading.js +139 -0
- package/dist/referrer-rule.d.ts +67 -0
- package/dist/referrer-rule.d.ts.map +1 -0
- package/dist/referrer-rule.js +96 -0
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +1 -1
- package/dist/release/payload-digest.d.ts +7 -3
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +7 -3
- package/dist/release/version-stamp.d.ts +16 -0
- package/dist/release/version-stamp.d.ts.map +1 -1
- package/dist/release/version-stamp.js +52 -0
- package/dist/release/workspace-config.d.ts +10 -6
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +10 -6
- package/dist/resolve-schema-ref-kinds.d.ts +4 -3
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +16 -0
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +4 -3
- package/dist/resolve-zone-containment.d.ts +99 -0
- package/dist/resolve-zone-containment.d.ts.map +1 -0
- package/dist/resolve-zone-containment.js +212 -0
- package/dist/resolve-zone-requirements.d.ts +5 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +48 -7
- package/dist/resource-rule.d.ts +106 -0
- package/dist/resource-rule.d.ts.map +1 -0
- package/dist/resource-rule.js +236 -0
- package/dist/rule-condition.d.ts +50 -0
- package/dist/rule-condition.d.ts.map +1 -0
- package/dist/rule-condition.js +108 -0
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +20 -0
- package/dist/schema-projection.d.ts +171 -0
- package/dist/schema-projection.d.ts.map +1 -0
- package/dist/schema-projection.js +317 -0
- package/dist/step-slot.d.ts +39 -0
- package/dist/step-slot.d.ts.map +1 -0
- package/dist/step-slot.js +49 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +17 -3
- package/dist/validate-durable-regions.d.ts +15 -0
- package/dist/validate-durable-regions.d.ts.map +1 -0
- package/dist/validate-durable-regions.js +336 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +44 -2
- package/dist/validate-referrer-rules.d.ts +120 -0
- package/dist/validate-referrer-rules.d.ts.map +1 -0
- package/dist/validate-referrer-rules.js +255 -0
- package/dist/validate-resource-rules.d.ts +114 -0
- package/dist/validate-resource-rules.d.ts.map +1 -0
- package/dist/validate-resource-rules.js +283 -0
- package/dist/validate-schema-projection.d.ts +32 -0
- package/dist/validate-schema-projection.d.ts.map +1 -0
- package/dist/validate-schema-projection.js +110 -0
- package/dist/validate-step-inputs.d.ts +2 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +5 -3
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +11 -5
- package/dist/validate-zone-slots.d.ts +2 -2
- package/dist/validate-zone-slots.d.ts.map +1 -1
- package/dist/validate-zone-slots.js +202 -6
- package/dist/validate-zone-violations.d.ts +12 -0
- package/dist/validate-zone-violations.d.ts.map +1 -0
- package/dist/validate-zone-violations.js +169 -0
- package/dist/value-type-keyword.d.ts +1 -1
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +4 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -1
- package/dist/zone-slot.js +86 -5
- package/package.json +2 -2
- package/src/analysis-registry.ts +6 -0
- package/src/analyzer.ts +395 -14
- package/src/call-graph.ts +2 -8
- package/src/flatten-for-analyzer.ts +1 -1
- package/src/import-resolution-diagnostics.ts +25 -7
- package/src/index.ts +73 -0
- package/src/invocation-contract.ts +45 -2
- package/src/loaded-types.ts +12 -3
- package/src/manifest-loader.ts +87 -55
- package/src/manifest-schemas.ts +328 -14
- package/src/module-compatibility.ts +72 -0
- package/src/ref-slot-reading.ts +156 -0
- package/src/referrer-rule.ts +116 -0
- package/src/release/index.ts +1 -0
- package/src/release/payload-digest.ts +7 -3
- package/src/release/version-stamp.ts +58 -0
- package/src/release/workspace-config.ts +10 -6
- package/src/resolve-schema-ref-kinds.ts +28 -4
- package/src/resolve-throws-union.ts +4 -3
- package/src/resolve-zone-containment.ts +329 -0
- package/src/resolve-zone-requirements.ts +67 -7
- package/src/resource-rule.ts +252 -0
- package/src/rule-condition.ts +126 -0
- package/src/schema-keywords.ts +24 -0
- package/src/schema-projection.ts +403 -0
- package/src/step-slot.ts +64 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +17 -3
- package/src/validate-durable-regions.ts +401 -0
- package/src/validate-references.ts +57 -2
- package/src/validate-referrer-rules.ts +373 -0
- package/src/validate-resource-rules.ts +401 -0
- package/src/validate-schema-projection.ts +184 -0
- package/src/validate-step-inputs.ts +5 -3
- package/src/validate-throws-coverage.ts +12 -5
- package/src/validate-zone-slots.ts +192 -8
- package/src/validate-zone-violations.ts +190 -0
- package/src/value-type-keyword.ts +4 -0
- package/src/zone-slot.ts +114 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static validation of the schema-projection annotations themselves — the
|
|
3
|
+
* strict half of the accessor split, mirroring `validate-ref-slots.ts` and
|
|
4
|
+
* `validate-zone-slots.ts`.
|
|
5
|
+
*
|
|
6
|
+
* `readSchemaProjection` / `readSchemaMap` are deliberately lenient: anything
|
|
7
|
+
* they cannot read reads as absent. Without this pass that leniency is silent in
|
|
8
|
+
* the direction that matters — a projection nothing can read does not fail, it
|
|
9
|
+
* simply stops typing the consumers that were counting on it, so a misspelled
|
|
10
|
+
* column reaches the database instead of `telo check`. The whole point of the
|
|
11
|
+
* projection is to move that failure earlier; an unreported malformed
|
|
12
|
+
* declaration puts it back.
|
|
13
|
+
*
|
|
14
|
+
* A map that is merely INCOMPLETE is the same failure per value: a declared type
|
|
15
|
+
* with no entry projects to nothing, so a column of that type silently vanishes
|
|
16
|
+
* from every consumer's view of the row.
|
|
17
|
+
*
|
|
18
|
+
* Scoping follows `X_TELO_REF_UNRESOLVED`: reported only for definitions in the
|
|
19
|
+
* entry's own modules — a published dependency's annotation is not the
|
|
20
|
+
* consumer's to fix.
|
|
21
|
+
*
|
|
22
|
+
* Browser-safe: no Node built-ins.
|
|
23
|
+
*/
|
|
24
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
25
|
+
export interface SchemaProjectionIssue {
|
|
26
|
+
code: "SCHEMA_PROJECTION_INVALID" | "SCHEMA_MAP_INCOMPLETE";
|
|
27
|
+
manifest: ResourceManifest;
|
|
28
|
+
path: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function validateSchemaProjection(manifest: ResourceManifest): SchemaProjectionIssue[];
|
|
32
|
+
//# sourceMappingURL=validate-schema-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-schema-projection.d.ts","sourceRoot":"","sources":["../src/validate-schema-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,2BAA2B,GAAG,uBAAuB,CAAC;IAC5D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,qBAAqB,EAAE,CA2G5F"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { projectionKeyMap, rawSchemaProjection, readSchemaProjection, schemaProjectionIsMisplaced, } from "./schema-projection.js";
|
|
2
|
+
const PROJECTION = "x-telo-schema-projection";
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function validateSchemaProjection(manifest) {
|
|
7
|
+
const doc = manifest;
|
|
8
|
+
const raw = rawSchemaProjection(doc);
|
|
9
|
+
if (raw === undefined)
|
|
10
|
+
return [];
|
|
11
|
+
const misplaced = schemaProjectionIsMisplaced(doc);
|
|
12
|
+
const base = misplaced ? `schema.${PROJECTION}` : PROJECTION;
|
|
13
|
+
const issue = (code, path, message) => ({ code, manifest, path, message });
|
|
14
|
+
const issues = [];
|
|
15
|
+
// Read from both positions so a misplaced annotation still WORKS, and
|
|
16
|
+
// reported so it does not stay misplaced. The alternative — reading only the
|
|
17
|
+
// document — makes the inner spelling silently inert, and the resulting
|
|
18
|
+
// failure surfaces on a consumer's slot as "this kind declares no projection",
|
|
19
|
+
// which blames an author who wrote the annotation correctly enough to mean it.
|
|
20
|
+
if (misplaced) {
|
|
21
|
+
issues.push(issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' belongs on the kind DOCUMENT, beside 'schema:', not inside it — ` +
|
|
22
|
+
`it describes the whole declaration rather than one field. It is read from here ` +
|
|
23
|
+
`too, so nothing is broken; move it up a level.`));
|
|
24
|
+
}
|
|
25
|
+
if (!isObject(raw)) {
|
|
26
|
+
return [
|
|
27
|
+
...issues,
|
|
28
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' must be an object naming the entry collection and the field that keys it.`),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
const missing = ["entries", "key"].filter((field) => typeof raw[field] !== "string");
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
...issues,
|
|
35
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' is missing ${missing.map((f) => `'${f}'`).join(" and ")}. ` +
|
|
36
|
+
`'entries' is a JSON Pointer to the collection, 'key' the entry field whose value ` +
|
|
37
|
+
`selects an 'x-telo-schema-map' entry.`),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const projection = readSchemaProjection(doc);
|
|
41
|
+
const schema = doc.schema;
|
|
42
|
+
// An ARRAY-shaped collection has no key to name its entries by, so without
|
|
43
|
+
// `name` every entry projects to nothing and the whole projection is an object
|
|
44
|
+
// schema with no properties and `additionalProperties: false` — one that
|
|
45
|
+
// rejects every value. A keyed map needs no `name`: the map key IS the
|
|
46
|
+
// identity.
|
|
47
|
+
if (collectionIsArray(schema, projection.entries) && !projection.nameField) {
|
|
48
|
+
return [
|
|
49
|
+
...issues,
|
|
50
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' names '${projection.entries}', which is an array, but declares no ` +
|
|
51
|
+
`'name'. An array's entries have no key, so 'name' must say which field holds an ` +
|
|
52
|
+
`entry's identity — without it the projection yields a schema that rejects every value.`),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
const map = projectionKeyMap(schema, projection);
|
|
56
|
+
if (!map) {
|
|
57
|
+
return [
|
|
58
|
+
...issues,
|
|
59
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' names '${projection.entries}' keyed on '${projection.key}', but that ` +
|
|
60
|
+
`field carries no 'x-telo-schema-map'. The map declares the JSON Schema each of its ` +
|
|
61
|
+
`values means, and without it the projection types nothing.`),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
// An `enum` on the keyed field is the kind's own closed vocabulary, so every
|
|
65
|
+
// member of it is a value the map has to answer for. Only an enum is checked:
|
|
66
|
+
// an open string field has no set to be complete against.
|
|
67
|
+
const keyField = keySchema(schema, projection.entries, projection.key);
|
|
68
|
+
const values = Array.isArray(keyField?.enum) ? keyField.enum : [];
|
|
69
|
+
const unmapped = values.filter((value) => typeof value === "string" && !(value in map));
|
|
70
|
+
if (unmapped.length === 0)
|
|
71
|
+
return issues;
|
|
72
|
+
return [
|
|
73
|
+
...issues,
|
|
74
|
+
issue("SCHEMA_MAP_INCOMPLETE", base, `'x-telo-schema-map' has no entry for ${unmapped.map((v) => `'${v}'`).join(", ")}. ` +
|
|
75
|
+
`An entry declaring an unmapped value projects to nothing, so it disappears from every ` +
|
|
76
|
+
`consumer's view of the shape.`),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
function collectionIsArray(schema, entries) {
|
|
80
|
+
let node = schema;
|
|
81
|
+
for (const segment of entries.split("/")) {
|
|
82
|
+
if (segment === "")
|
|
83
|
+
continue;
|
|
84
|
+
if (!isObject(node) || !isObject(node.properties))
|
|
85
|
+
return false;
|
|
86
|
+
node = node.properties[segment];
|
|
87
|
+
}
|
|
88
|
+
return isObject(node) && isObject(node.items);
|
|
89
|
+
}
|
|
90
|
+
function keySchema(schema, entries, key) {
|
|
91
|
+
let node = schema;
|
|
92
|
+
for (const segment of entries.split("/")) {
|
|
93
|
+
if (segment === "")
|
|
94
|
+
continue;
|
|
95
|
+
if (!isObject(node) || !isObject(node.properties))
|
|
96
|
+
return undefined;
|
|
97
|
+
node = node.properties[segment];
|
|
98
|
+
}
|
|
99
|
+
if (!isObject(node))
|
|
100
|
+
return undefined;
|
|
101
|
+
const entry = isObject(node.additionalProperties)
|
|
102
|
+
? node.additionalProperties
|
|
103
|
+
: isObject(node.items)
|
|
104
|
+
? node.items
|
|
105
|
+
: undefined;
|
|
106
|
+
if (!isObject(entry) || !isObject(entry.properties))
|
|
107
|
+
return undefined;
|
|
108
|
+
const field = entry.properties[key];
|
|
109
|
+
return isObject(field) ? field : undefined;
|
|
110
|
+
}
|
|
@@ -20,7 +20,8 @@ export interface StepInputIssue {
|
|
|
20
20
|
* CEL leaves are replaced by schema-shaped placeholders first (`substituteCelFields`),
|
|
21
21
|
* so an expression is never a false positive — only structural disagreement is
|
|
22
22
|
* reported. Nothing is hardcoded about `Run.Sequence`: the invoke field comes
|
|
23
|
-
* from
|
|
23
|
+
* from the step slot (the shared grammar, or the legacy `x-telo-step-context`),
|
|
24
|
+
* and the paired inputs field from whichever sibling
|
|
24
25
|
* property carries `x-telo-topology-role: inputs`.
|
|
25
26
|
*/
|
|
26
27
|
export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAsBnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,GAAG,oBAAoB,CAAC;CAC5D;AAGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,CAsJlB"}
|
|
@@ -4,6 +4,7 @@ import { plainChainOf } from "@telorun/templating";
|
|
|
4
4
|
import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
|
|
5
5
|
import { manifestFragmentOf } from "./manifest-schemas.js";
|
|
6
6
|
import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
|
|
7
|
+
import { readStepSlot } from "./step-slot.js";
|
|
7
8
|
/**
|
|
8
9
|
* Validate every step's `inputs:` against the invoked target's declared input
|
|
9
10
|
* contract — the static half of what the kernel enforces at dispatch.
|
|
@@ -16,7 +17,8 @@ import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequi
|
|
|
16
17
|
* CEL leaves are replaced by schema-shaped placeholders first (`substituteCelFields`),
|
|
17
18
|
* so an expression is never a false positive — only structural disagreement is
|
|
18
19
|
* reported. Nothing is hardcoded about `Run.Sequence`: the invoke field comes
|
|
19
|
-
* from
|
|
20
|
+
* from the step slot (the shared grammar, or the legacy `x-telo-step-context`),
|
|
21
|
+
* and the paired inputs field from whichever sibling
|
|
20
22
|
* property carries `x-telo-topology-role: inputs`.
|
|
21
23
|
*/
|
|
22
24
|
export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes,
|
|
@@ -31,8 +33,8 @@ stepContext) {
|
|
|
31
33
|
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
32
34
|
const readingModule = manifest.metadata?.module;
|
|
33
35
|
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
34
|
-
const stepCtx = fieldSchema
|
|
35
|
-
if (!stepCtx
|
|
36
|
+
const stepCtx = readStepSlot(fieldSchema);
|
|
37
|
+
if (!stepCtx)
|
|
36
38
|
continue;
|
|
37
39
|
const steps = manifest[fieldName];
|
|
38
40
|
if (!Array.isArray(steps))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
|
|
@@ -4,6 +4,7 @@ import { scopeResolverForModule } from "./alias-resolver.js";
|
|
|
4
4
|
import { createResolveCtx, resolveThrowsUnion, } from "./resolve-throws-union.js";
|
|
5
5
|
import { DiagnosticSeverity } from "./types.js";
|
|
6
6
|
import { extractAccessChains, validateChainAgainstSchema } from "./validate-cel-context.js";
|
|
7
|
+
import { isStepSlot } from "./step-slot.js";
|
|
7
8
|
const SOURCE = "telo-analyzer";
|
|
8
9
|
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
9
10
|
/** Walk `definition.schema` and `data` in tandem, invoking `onOutcome` each
|
|
@@ -366,9 +367,11 @@ function checkCelChainAgainstDataSchema(entry, dataSchema, resource, filePath, e
|
|
|
366
367
|
return diagnostics;
|
|
367
368
|
}
|
|
368
369
|
/** Rule 8 extension: `inherit: true` only makes sense on a definition whose
|
|
369
|
-
* schema
|
|
370
|
-
*
|
|
371
|
-
*
|
|
370
|
+
* schema declares at least one STEP BODY — an array whose items point at the
|
|
371
|
+
* shared grammar, or, for a module published before that fragment existed, one
|
|
372
|
+
* carrying the legacy `x-telo-step-context` annotation. That is what drives the
|
|
373
|
+
* resolver's generic step traversal; a definition with `inherit: true` and no
|
|
374
|
+
* such array has no invocables to inherit from. */
|
|
372
375
|
function validateThrowsDeclarations(manifests) {
|
|
373
376
|
const diagnostics = [];
|
|
374
377
|
for (const m of manifests) {
|
|
@@ -386,7 +389,10 @@ function validateThrowsDeclarations(manifests) {
|
|
|
386
389
|
severity: DiagnosticSeverity.Error,
|
|
387
390
|
code: "INHERIT_WITHOUT_STEP_CONTEXT",
|
|
388
391
|
source: SOURCE,
|
|
389
|
-
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema
|
|
392
|
+
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema declares no step ` +
|
|
393
|
+
`body. inherit is only meaningful on a definition that drives invocables through steps — ` +
|
|
394
|
+
`give an array field 'items: { $ref: "telo://manifest#/$defs/Step" }' (the legacy ` +
|
|
395
|
+
`x-telo-step-context annotation is also recognised).`,
|
|
390
396
|
data: { resource: { kind: m.kind, name }, filePath, path: "throws.inherit" },
|
|
391
397
|
});
|
|
392
398
|
}
|
|
@@ -397,7 +403,7 @@ function validateThrowsDeclarations(manifests) {
|
|
|
397
403
|
function schemaHasStepContext(schema) {
|
|
398
404
|
if (!schema || typeof schema !== "object")
|
|
399
405
|
return false;
|
|
400
|
-
if (
|
|
406
|
+
if (isStepSlot(schema))
|
|
401
407
|
return true;
|
|
402
408
|
const props = schema.properties;
|
|
403
409
|
if (props && typeof props === "object") {
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Browser-safe: no Node built-ins.
|
|
28
28
|
*/
|
|
29
|
-
import type
|
|
29
|
+
import { type ResourceManifest } from "@telorun/sdk";
|
|
30
30
|
export interface ZoneSlotIssue {
|
|
31
|
-
code: "ZONE_ANNOTATION_INVALID";
|
|
31
|
+
code: "ZONE_ANNOTATION_INVALID" | "ZONE_ATTRIBUTE_UNKNOWN" | "ZONE_ATTRIBUTE_INCOMPLETE";
|
|
32
32
|
manifest: ResourceManifest;
|
|
33
33
|
/** Schema path of the annotated slot. */
|
|
34
34
|
path: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-zone-slots.d.ts","sourceRoot":"","sources":["../src/validate-zone-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validate-zone-slots.d.ts","sourceRoot":"","sources":["../src/validate-zone-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG1F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,yBAAyB,GAAG,wBAAwB,GAAG,2BAA2B,CAAC;IACzF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA0TD,iFAAiF;AACjF,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAM1F"}
|
|
@@ -1,5 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static validation of the two execution-zone annotations themselves — the
|
|
3
|
+
* strict half of the accessor split, mirroring `validate-ref-slots.ts`.
|
|
4
|
+
*
|
|
5
|
+
* `readProvidesZone` / `readRequiresZone` are deliberately lenient: they return
|
|
6
|
+
* `undefined` for anything they cannot read. Without this pass that leniency is
|
|
7
|
+
* silent in the worst possible direction, because the two annotations fail in
|
|
8
|
+
* OPPOSITE ways:
|
|
9
|
+
*
|
|
10
|
+
* - an unreadable **requires** annotation drops the requirement entirely, so a
|
|
11
|
+
* safety constraint the author wrote is never enforced — and the resource
|
|
12
|
+
* then throws `ERR_ZONE_REQUIRED` / `ERR_ZONE_ANNOTATION_MISSING` at
|
|
13
|
+
* dispatch. That is exactly the silent-non-enforcement `ZONE_PROVIDER_UNRESOLVED`
|
|
14
|
+
* exists to prevent, reached by a different route.
|
|
15
|
+
* - an unreadable **provides** annotation drops the discharge, so the pass
|
|
16
|
+
* reports `ZONE_REQUIREMENT_UNSATISFIED` on manifests that are correct.
|
|
17
|
+
*
|
|
18
|
+
* A third shape is worse than either: a `key` the analyzer skips but the kernel
|
|
19
|
+
* accepts (a pointer with no leading `/` — the kernel's walk splits on `/` and
|
|
20
|
+
* drops empty segments, so it resolves) makes the two halves disagree about what
|
|
21
|
+
* the manifest MEANS, which is the one outcome neither severity can express.
|
|
22
|
+
*
|
|
23
|
+
* Scoping follows `X_TELO_REF_UNRESOLVED`: reported only for definitions in the
|
|
24
|
+
* entry's own modules — a published dependency's slot is not the consumer's to
|
|
25
|
+
* fix.
|
|
26
|
+
*
|
|
27
|
+
* Browser-safe: no Node built-ins.
|
|
28
|
+
*/
|
|
29
|
+
import { ZONE_ATTRIBUTES, zoneAttributeNames } from "@telorun/sdk";
|
|
30
|
+
import { distance } from "./levenshtein.js";
|
|
1
31
|
const PROVIDES = "x-telo-provides-zone";
|
|
2
32
|
const REQUIRES = "x-telo-requires-zone";
|
|
33
|
+
const VIOLATES = "x-telo-violates-zone";
|
|
3
34
|
/** A self-relative JSON Pointer, the only correlation-key spelling both halves
|
|
4
35
|
* read identically. `""` (whole document) is meaningless as a key, so a
|
|
5
36
|
* pointer must name at least one segment. */
|
|
@@ -15,20 +46,133 @@ function describe(value) {
|
|
|
15
46
|
return "null";
|
|
16
47
|
return typeof value;
|
|
17
48
|
}
|
|
49
|
+
/** The closest declared attribute name within an edit-distance threshold, or
|
|
50
|
+
* undefined. Mirrors `suggestValueType`: case-sensitive, and silent on a tie,
|
|
51
|
+
* because a coin-flip suggestion is worse than none. */
|
|
52
|
+
function suggestAttribute(name) {
|
|
53
|
+
if (!name)
|
|
54
|
+
return undefined;
|
|
55
|
+
const threshold = Math.min(3, Math.floor(name.length / 3));
|
|
56
|
+
if (threshold < 1)
|
|
57
|
+
return undefined;
|
|
58
|
+
let best;
|
|
59
|
+
let bestDist = threshold + 1;
|
|
60
|
+
let tied = false;
|
|
61
|
+
for (const candidate of ZONE_ATTRIBUTES.keys()) {
|
|
62
|
+
const d = distance(name, candidate);
|
|
63
|
+
if (d < bestDist) {
|
|
64
|
+
best = candidate;
|
|
65
|
+
bestDist = d;
|
|
66
|
+
tied = false;
|
|
67
|
+
}
|
|
68
|
+
else if (d === bestDist) {
|
|
69
|
+
tied = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return !best || bestDist > threshold || tied ? undefined : best;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The attributes half of the object form.
|
|
76
|
+
*
|
|
77
|
+
* The composed `additionalProperties: false` schema would already reject an
|
|
78
|
+
* unknown name; the dedicated code earns its place by NAMING the valid ones and
|
|
79
|
+
* suggesting a spelling, instead of reporting a schema violation on a key the
|
|
80
|
+
* author believed was real — the same standing `X_TELO_TYPE_UNKNOWN` has over
|
|
81
|
+
* the value-type vocabulary it is modelled on.
|
|
82
|
+
*/
|
|
83
|
+
function checkAttributes(obj, definition, path, issues, annotation = PROVIDES) {
|
|
84
|
+
const declared = new Set();
|
|
85
|
+
for (const [name, value] of Object.entries(obj)) {
|
|
86
|
+
if (name === "key")
|
|
87
|
+
continue;
|
|
88
|
+
const entry = ZONE_ATTRIBUTES.get(name);
|
|
89
|
+
if (!entry) {
|
|
90
|
+
const suggestion = suggestAttribute(name);
|
|
91
|
+
issues.push({
|
|
92
|
+
code: "ZONE_ATTRIBUTE_UNKNOWN",
|
|
93
|
+
manifest: definition,
|
|
94
|
+
path,
|
|
95
|
+
message: `${annotation} at '${path}' declares '${name}', which is not a zone attribute. ` +
|
|
96
|
+
(suggestion ? `Did you mean '${suggestion}'? ` : "") +
|
|
97
|
+
`The vocabulary is closed: ${zoneAttributeNames().join(", ")}. An attribute ` +
|
|
98
|
+
`states a property of everything executed inside this zone, and every reader ` +
|
|
99
|
+
`of one is core — a name outside the set would be read by nothing.`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Each value is the REASON, and it is required by being the value itself
|
|
104
|
+
// rather than a sibling of a boolean. That is also what makes a type check
|
|
105
|
+
// possible at all: there is no `true` to accept, so `atomic: true` is caught
|
|
106
|
+
// here rather than reading as a valid declaration with nothing to say.
|
|
107
|
+
if (typeof value !== "string" || !value) {
|
|
108
|
+
issues.push({
|
|
109
|
+
code: "ZONE_ANNOTATION_INVALID",
|
|
110
|
+
manifest: definition,
|
|
111
|
+
path,
|
|
112
|
+
message: `${annotation} at '${path}' declares '${name}' as ${describe(value)}. Every zone ` +
|
|
113
|
+
`attribute's value is the author's REASON — a non-empty sentence, quoted verbatim ` +
|
|
114
|
+
`by the diagnostics that enforce it (${entry.description}).`,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
declared.add(name);
|
|
119
|
+
}
|
|
120
|
+
// The completeness rule is about what a region PROMISES: declaring `atomic`
|
|
121
|
+
// without `noSuspend` is an incomplete promise. A VIOLATION is not a promise —
|
|
122
|
+
// breaking one guarantee implies nothing about breaking another — so the rule
|
|
123
|
+
// does not apply there.
|
|
124
|
+
if (annotation !== PROVIDES)
|
|
125
|
+
return;
|
|
126
|
+
// `requires:` lives in the vocabulary entry rather than as a hardcoded pair of
|
|
127
|
+
// names here, so the completeness rule sits beside the thing it constrains.
|
|
128
|
+
for (const name of declared) {
|
|
129
|
+
for (const dependency of ZONE_ATTRIBUTES.get(name).requires) {
|
|
130
|
+
if (declared.has(dependency))
|
|
131
|
+
continue;
|
|
132
|
+
issues.push({
|
|
133
|
+
code: "ZONE_ATTRIBUTE_INCOMPLETE",
|
|
134
|
+
manifest: definition,
|
|
135
|
+
path,
|
|
136
|
+
message: `${PROVIDES} at '${path}' declares '${name}' without '${dependency}', which it ` +
|
|
137
|
+
`requires. ${ZONE_ATTRIBUTES.get(dependency).description} Declare it with its own ` +
|
|
138
|
+
`reason — a generic message is exactly what the required reason exists to prevent.`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
18
143
|
function checkProvides(raw, definition, path, issues) {
|
|
19
144
|
if (raw === true)
|
|
20
145
|
return;
|
|
21
146
|
if (isPointer(raw))
|
|
22
147
|
return;
|
|
148
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
149
|
+
const obj = raw;
|
|
150
|
+
// `key` keeps the meaning it has in the scalar spelling; only its absence is
|
|
151
|
+
// legitimate here (an uncorrelated zone that still declares attributes).
|
|
152
|
+
if (obj.key !== undefined && !isPointer(obj.key)) {
|
|
153
|
+
issues.push({
|
|
154
|
+
code: "ZONE_ANNOTATION_INVALID",
|
|
155
|
+
manifest: definition,
|
|
156
|
+
path,
|
|
157
|
+
message: `${PROVIDES} at '${path}' declares the correlation key ${describe(obj.key)}, which ` +
|
|
158
|
+
`is not a self-relative JSON Pointer. Write '/connection'. A bare field name is ` +
|
|
159
|
+
`read as a pointer by the runtime but skipped by the checker, so the two halves ` +
|
|
160
|
+
`would disagree about what this manifest means.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
checkAttributes(obj, definition, path, issues);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
23
166
|
issues.push({
|
|
24
167
|
code: "ZONE_ANNOTATION_INVALID",
|
|
25
168
|
manifest: definition,
|
|
26
169
|
path,
|
|
27
170
|
message: `${PROVIDES} at '${path}' is ${describe(raw)}. It takes 'true' (the zone is ` +
|
|
28
|
-
`uncorrelated)
|
|
171
|
+
`uncorrelated), a self-relative JSON Pointer naming this kind's own field ` +
|
|
29
172
|
`whose resolved reference the zone carries as its correlation payload ` +
|
|
30
|
-
`(e.g. '/connection')
|
|
31
|
-
`
|
|
173
|
+
`(e.g. '/connection'), or an object carrying that pointer as 'key' beside the ` +
|
|
174
|
+
`zone attributes this region declares (${zoneAttributeNames().join(", ")}). It ` +
|
|
175
|
+
`never names the zone — the zone a slot provides is always the declaring kind.`,
|
|
32
176
|
});
|
|
33
177
|
}
|
|
34
178
|
function checkRequires(raw, definition, path, issues) {
|
|
@@ -72,13 +216,61 @@ function checkRequires(raw, definition, path, issues) {
|
|
|
72
216
|
if (obj.reason !== undefined && typeof obj.reason !== "string") {
|
|
73
217
|
fail(`${REQUIRES} at '${path}' declares a non-string 'reason'.`);
|
|
74
218
|
}
|
|
219
|
+
// `attributes` is the GUARANTEE half — what the satisfying zone must declare,
|
|
220
|
+
// as opposed to which kind it is. It is a list of NAMES with no reasons: the
|
|
221
|
+
// reason belongs to whoever makes the promise, and a requirer restating it
|
|
222
|
+
// would be a second sentence to keep in step with the one that is enforced.
|
|
223
|
+
if (obj.attributes !== undefined) {
|
|
224
|
+
if (!Array.isArray(obj.attributes) || obj.attributes.length === 0) {
|
|
225
|
+
fail(`${REQUIRES} at '${path}' declares 'attributes' as ${describe(obj.attributes)}. It ` +
|
|
226
|
+
`takes a non-empty list of zone attribute names; omit it to require the kind alone.`);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
for (const name of obj.attributes) {
|
|
230
|
+
if (typeof name === "string" && ZONE_ATTRIBUTES.has(name))
|
|
231
|
+
continue;
|
|
232
|
+
const suggestion = typeof name === "string" ? suggestAttribute(name) : undefined;
|
|
233
|
+
issues.push({
|
|
234
|
+
code: "ZONE_ATTRIBUTE_UNKNOWN",
|
|
235
|
+
manifest: definition,
|
|
236
|
+
path,
|
|
237
|
+
message: `${REQUIRES} at '${path}' requires '${String(name)}', which is not a zone ` +
|
|
238
|
+
`attribute. ` +
|
|
239
|
+
(suggestion ? `Did you mean '${suggestion}'? ` : "") +
|
|
240
|
+
`The vocabulary is closed: ${zoneAttributeNames().join(", ")}.`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
75
245
|
for (const key of Object.keys(obj)) {
|
|
76
|
-
if (key === "zone" || key === "key" || key === "reason") {
|
|
246
|
+
if (key === "zone" || key === "key" || key === "reason" || key === "attributes") {
|
|
77
247
|
continue;
|
|
78
248
|
}
|
|
79
249
|
fail(`${REQUIRES} at '${path}' declares an unknown property '${key}'. The object form takes ` +
|
|
80
|
-
`'zone', 'key' and '
|
|
250
|
+
`'zone', 'key', 'reason' and 'attributes'.`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The third annotation: what this kind cannot honour.
|
|
255
|
+
*
|
|
256
|
+
* Only the object form, because a violation without a reason is exactly the
|
|
257
|
+
* shape the vocabulary refuses everywhere else — the diagnostic that fires on it
|
|
258
|
+
* prints the region's promise and this resource's rebuttal side by side, and one
|
|
259
|
+
* of the two would otherwise be a generated sentence.
|
|
260
|
+
*/
|
|
261
|
+
function checkViolates(raw, definition, path, issues) {
|
|
262
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
263
|
+
issues.push({
|
|
264
|
+
code: "ZONE_ANNOTATION_INVALID",
|
|
265
|
+
manifest: definition,
|
|
266
|
+
path,
|
|
267
|
+
message: `${VIOLATES} at '${path}' is ${describe(raw)}. It takes a map of zone attribute ` +
|
|
268
|
+
`names to the reason this kind cannot honour each — ` +
|
|
269
|
+
`\`noSuspend: this waits for a delivery that may be days away\`.`,
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
81
272
|
}
|
|
273
|
+
checkAttributes(raw, definition, path, issues, VIOLATES);
|
|
82
274
|
}
|
|
83
275
|
/** Walk a definition schema, reporting every zone annotation it cannot read.
|
|
84
276
|
* Pure-schema walk, so it needs a visited guard for cyclic `$defs`. */
|
|
@@ -97,8 +289,12 @@ function walkSchema(node, path, visited, definition, issues) {
|
|
|
97
289
|
checkProvides(obj[PROVIDES], definition, path, issues);
|
|
98
290
|
if (obj[REQUIRES] !== undefined)
|
|
99
291
|
checkRequires(obj[REQUIRES], definition, path, issues);
|
|
292
|
+
if (obj[VIOLATES] !== undefined)
|
|
293
|
+
checkViolates(obj[VIOLATES], definition, path, issues);
|
|
100
294
|
for (const [key, value] of Object.entries(obj)) {
|
|
101
|
-
if (key === PROVIDES || key === REQUIRES || key ===
|
|
295
|
+
if (key === PROVIDES || key === REQUIRES || key === VIOLATES)
|
|
296
|
+
continue;
|
|
297
|
+
if (key === "examples" || key === "default")
|
|
102
298
|
continue;
|
|
103
299
|
walkSchema(value, path ? `${path}.${key}` : key, visited, definition, issues);
|
|
104
300
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CallGraph } from "./call-graph.js";
|
|
2
|
+
import { type DefinitionLookup } from "./resolve-zone-containment.js";
|
|
3
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
4
|
+
export interface ZoneViolationArgs {
|
|
5
|
+
graph: CallGraph;
|
|
6
|
+
resolveDef: DefinitionLookup;
|
|
7
|
+
/** Only report against modules the entry owns — a published dependency's
|
|
8
|
+
* placement is not the consumer's to fix. */
|
|
9
|
+
reportModules: ReadonlySet<string>;
|
|
10
|
+
}
|
|
11
|
+
export declare function validateZoneViolations(args: ZoneViolationArgs): AnalysisDiagnostic[];
|
|
12
|
+
//# sourceMappingURL=validate-zone-violations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-zone-violations.d.ts","sourceRoot":"","sources":["../src/validate-zone-violations.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKzE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B;kDAC8C;IAC9C,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,EAAE,CA8FpF"}
|