@cloudcrane/app-spec 0.1.1 → 0.2.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/README.md +5 -0
- package/dist/src/catalog.js +43 -1
- package/dist/src/leak.js +3 -1
- package/dist/src/schema.d.ts +97 -0
- package/dist/src/schema.js +31 -1
- package/dist/src/validate.js +30 -1
- package/package.json +1 -1
- package/schema/app-spec.v1.json +73 -0
package/README.md
CHANGED
|
@@ -18,6 +18,11 @@ if (!spec) for (const p of problems) console.log(`${p.at}: ${p.problem}`);
|
|
|
18
18
|
- `Rules` (`@cloudcrane/app-spec/logic`): evaluates a spec's CEL rules, with
|
|
19
19
|
date functions and a bounded `fold`.
|
|
20
20
|
- `@cloudcrane/app-spec/schema.json`: the spec as JSON Schema (2020-12).
|
|
21
|
+
- Coded components (`components`): code for what the catalog can't draw, with
|
|
22
|
+
the kit it runs with (`game`), the inputs it's given and the events it sends.
|
|
23
|
+
A screen places one with the catalog's `Coded` component, binding each input
|
|
24
|
+
to the data model and each event to an action. Its code is exempt from the
|
|
25
|
+
leak scan's code rules, but not from the others.
|
|
21
26
|
|
|
22
27
|
`zod` 3.25 is a peer dependency. Includes A2UI v0.9.1's schemas, unchanged,
|
|
23
28
|
under the Apache License 2.0 (`vendor/a2ui`). MIT licensed.
|
package/dist/src/catalog.js
CHANGED
|
@@ -10,6 +10,8 @@ import { BASIC_CATALOG } from "./a2ui.js";
|
|
|
10
10
|
// Components:
|
|
11
11
|
// DateStrip: the last N days as chips, today on the right, one selectable,
|
|
12
12
|
// each with a status dot.
|
|
13
|
+
// Coded: places one of the spec's coded components, sandboxed in a frame; its
|
|
14
|
+
// state shows at /ui/coded/<id>/state.
|
|
13
15
|
export const CLOUDCRANE_CATALOG_ID = "https://cloudcrane.ai/catalogs/app/v1.json";
|
|
14
16
|
const COMMON = "https://a2ui.org/specification/v0_9/common_types.json#/$defs";
|
|
15
17
|
const path = { $ref: `${COMMON}/DynamicString`, description: "A JSON Pointer into the data model, like /saved/days/2026-10-27." };
|
|
@@ -60,6 +62,46 @@ const COMPONENTS = {
|
|
|
60
62
|
],
|
|
61
63
|
unevaluatedProperties: false,
|
|
62
64
|
},
|
|
65
|
+
Coded: {
|
|
66
|
+
type: "object",
|
|
67
|
+
allOf: [
|
|
68
|
+
{ $ref: `${COMMON}/ComponentCommon` },
|
|
69
|
+
{ $ref: "#/$defs/CatalogComponentCommon" },
|
|
70
|
+
{
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
component: { const: "Coded" },
|
|
74
|
+
use: { type: "string", pattern: "^[a-z][a-zA-Z0-9_.-]{0,63}$", description: "The coded component to place, by its name in the spec's components." },
|
|
75
|
+
label: { $ref: `${COMMON}/DynamicString`, description: "What it is, for people who can't see it." },
|
|
76
|
+
aspect: { type: "number", minimum: 0.25, maximum: 4, description: "Width over height; 1 (square) if left out." },
|
|
77
|
+
inputs: {
|
|
78
|
+
type: "array",
|
|
79
|
+
maxItems: 10,
|
|
80
|
+
description: "What it's given, each a declared input bound to the data model.",
|
|
81
|
+
items: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: { name: { type: "string" }, value: { $ref: `${COMMON}/DynamicValue` } },
|
|
84
|
+
required: ["name", "value"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
on: {
|
|
89
|
+
type: "array",
|
|
90
|
+
maxItems: 10,
|
|
91
|
+
description: "What happens when it sends a declared event; the event's fields are at /ui/coded/<this id>/<event> while the action runs.",
|
|
92
|
+
items: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: { name: { type: "string" }, action: { $ref: `${COMMON}/Action` } },
|
|
95
|
+
required: ["name", "action"],
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["component", "use", "label"],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
unevaluatedProperties: false,
|
|
104
|
+
},
|
|
63
105
|
};
|
|
64
106
|
/** The CloudCrane catalog document, built from A2UI's basic catalog. */
|
|
65
107
|
export function cloudcraneCatalog() {
|
|
@@ -73,7 +115,7 @@ export function cloudcraneCatalog() {
|
|
|
73
115
|
$id: CLOUDCRANE_CATALOG_ID,
|
|
74
116
|
catalogId: CLOUDCRANE_CATALOG_ID,
|
|
75
117
|
title: "CloudCrane app catalog",
|
|
76
|
-
description: "A2UI's basic catalog, plus local actions (set, push, removeAt, batch) and
|
|
118
|
+
description: "A2UI's basic catalog, plus local actions (set, push, removeAt, batch), DateStrip and Coded, for apps that run on a phone.",
|
|
77
119
|
};
|
|
78
120
|
}
|
|
79
121
|
export const CLOUDCRANE_CATALOG = cloudcraneCatalog();
|
package/dist/src/leak.js
CHANGED
|
@@ -94,6 +94,8 @@ const RULES = [
|
|
|
94
94
|
const EXEMPT = new Set(["/manifest/source/fingerprint"]);
|
|
95
95
|
/** Where CEL lives: rules may look like code there, but nothing else may. */
|
|
96
96
|
const CEL_AT = [/^\/logic\/functions\/[^/]+\/cel$/, /^\/logic\/computed\/.+$/, /^\/data\/migrations\/\d+\/cel$/];
|
|
97
|
+
/** A coded component's code: code by design (it runs sandboxed), so only the code rules skip it. */
|
|
98
|
+
const CODE_AT = /^\/components\/[^/]+\/code$/;
|
|
97
99
|
/** A test step's state path: a dotted path into what the app exposes (CloudCrane's form), which reads like a member access by design. */
|
|
98
100
|
const STATE_PATH_AT = /^\/tests\/\d+\/(steps|setup|check)\/\d+\/path$/;
|
|
99
101
|
const STATE_PATH = /^[A-Za-z0-9_$]+(\.[A-Za-z0-9_$]+)*$/;
|
|
@@ -125,7 +127,7 @@ export function leakProblems(spec) {
|
|
|
125
127
|
continue;
|
|
126
128
|
// A2UI's bindings inside text (${/path}, ${fn(…)}) are the standard's syntax.
|
|
127
129
|
const s = at.startsWith("/surface/") || at.startsWith("/data/views/") ? raw.replace(/\$\{[^}]*\}/g, " ") : raw;
|
|
128
|
-
const codeShaped = CEL_AT.some((r) => r.test(at)) || (STATE_PATH_AT.test(at) && STATE_PATH.test(raw));
|
|
130
|
+
const codeShaped = CEL_AT.some((r) => r.test(at)) || CODE_AT.test(at) || (STATE_PATH_AT.test(at) && STATE_PATH.test(raw));
|
|
129
131
|
// Web addresses are checked as addresses (hosts, secrets), never as code.
|
|
130
132
|
const withoutUrls = s.replace(/https?:\/\/[^\s"'<>]+/g, " ");
|
|
131
133
|
for (const rule of RULES) {
|
package/dist/src/schema.d.ts
CHANGED
|
@@ -14,6 +14,45 @@ export type TokenNode = {
|
|
|
14
14
|
[name: string]: unknown;
|
|
15
15
|
};
|
|
16
16
|
export declare const Tokens: z.ZodType<TokenNode>;
|
|
17
|
+
/** The kinds of value a coded component is given or sends: JSON's, by name. */
|
|
18
|
+
export declare const CODED_TYPES: readonly ["number", "string", "boolean", "list", "map"];
|
|
19
|
+
/**
|
|
20
|
+
* A coded component: what the catalog can't draw, as the spec's own code, run
|
|
21
|
+
* sandboxed in a frame with a kit (`game`: a loop, a canvas, touch and keys).
|
|
22
|
+
* It's given only its declared inputs and may only send its declared events;
|
|
23
|
+
* a screen places it with the catalog's Coded component.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CodedComponent: z.ZodEffects<z.ZodObject<{
|
|
26
|
+
kind: z.ZodLiteral<"coded">;
|
|
27
|
+
kit: z.ZodEnum<["game"]>;
|
|
28
|
+
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>;
|
|
29
|
+
events: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>>;
|
|
30
|
+
code: z.ZodEffects<z.ZodString, string, string>;
|
|
31
|
+
}, "strict", z.ZodTypeAny, {
|
|
32
|
+
code: string;
|
|
33
|
+
kind: "coded";
|
|
34
|
+
kit: "game";
|
|
35
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
36
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
37
|
+
}, {
|
|
38
|
+
code: string;
|
|
39
|
+
kind: "coded";
|
|
40
|
+
kit: "game";
|
|
41
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
42
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
43
|
+
}>, {
|
|
44
|
+
code: string;
|
|
45
|
+
kind: "coded";
|
|
46
|
+
kit: "game";
|
|
47
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
48
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
49
|
+
}, {
|
|
50
|
+
code: string;
|
|
51
|
+
kind: "coded";
|
|
52
|
+
kit: "game";
|
|
53
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
54
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
55
|
+
}>;
|
|
17
56
|
export declare const SpecSchema: z.ZodObject<{
|
|
18
57
|
v: z.ZodLiteral<1>;
|
|
19
58
|
manifest: z.ZodObject<{
|
|
@@ -255,6 +294,50 @@ export declare const SpecSchema: z.ZodObject<{
|
|
|
255
294
|
}>, "many">>;
|
|
256
295
|
/** CloudCrane's test steps, as its blueprint tests take them; CloudCrane checks their shape. */
|
|
257
296
|
tests: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
297
|
+
/** Coded components, by name: the spec's own code for what the catalog can't draw. */
|
|
298
|
+
components: z.ZodDefault<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
|
|
299
|
+
kind: z.ZodLiteral<"coded">;
|
|
300
|
+
kit: z.ZodEnum<["game"]>;
|
|
301
|
+
inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>;
|
|
302
|
+
events: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>>;
|
|
303
|
+
code: z.ZodEffects<z.ZodString, string, string>;
|
|
304
|
+
}, "strict", z.ZodTypeAny, {
|
|
305
|
+
code: string;
|
|
306
|
+
kind: "coded";
|
|
307
|
+
kit: "game";
|
|
308
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
309
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
310
|
+
}, {
|
|
311
|
+
code: string;
|
|
312
|
+
kind: "coded";
|
|
313
|
+
kit: "game";
|
|
314
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
315
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
316
|
+
}>, {
|
|
317
|
+
code: string;
|
|
318
|
+
kind: "coded";
|
|
319
|
+
kit: "game";
|
|
320
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
321
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
322
|
+
}, {
|
|
323
|
+
code: string;
|
|
324
|
+
kind: "coded";
|
|
325
|
+
kit: "game";
|
|
326
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
327
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
328
|
+
}>>, Record<string, {
|
|
329
|
+
code: string;
|
|
330
|
+
kind: "coded";
|
|
331
|
+
kit: "game";
|
|
332
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
333
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
334
|
+
}>, Record<string, {
|
|
335
|
+
code: string;
|
|
336
|
+
kind: "coded";
|
|
337
|
+
kit: "game";
|
|
338
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
339
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
340
|
+
}>>>;
|
|
258
341
|
/** What a remix may change and what stays as the partner made it. */
|
|
259
342
|
mods: z.ZodDefault<z.ZodObject<{
|
|
260
343
|
open: z.ZodArray<z.ZodString, "many">;
|
|
@@ -267,6 +350,13 @@ export declare const SpecSchema: z.ZodObject<{
|
|
|
267
350
|
locked: string[];
|
|
268
351
|
}>>;
|
|
269
352
|
}, "strict", z.ZodTypeAny, {
|
|
353
|
+
components: Record<string, {
|
|
354
|
+
code: string;
|
|
355
|
+
kind: "coded";
|
|
356
|
+
kit: "game";
|
|
357
|
+
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
358
|
+
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
359
|
+
}>;
|
|
270
360
|
surface: {
|
|
271
361
|
catalog: string;
|
|
272
362
|
messages: Record<string, unknown>[];
|
|
@@ -400,6 +490,13 @@ export declare const SpecSchema: z.ZodObject<{
|
|
|
400
490
|
id: string;
|
|
401
491
|
}[];
|
|
402
492
|
}[];
|
|
493
|
+
components?: Record<string, {
|
|
494
|
+
code: string;
|
|
495
|
+
kind: "coded";
|
|
496
|
+
kit: "game";
|
|
497
|
+
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
498
|
+
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
499
|
+
}> | undefined;
|
|
403
500
|
network?: {
|
|
404
501
|
site: string;
|
|
405
502
|
why: string;
|
package/dist/src/schema.js
CHANGED
|
@@ -3,7 +3,9 @@ import { z } from "zod";
|
|
|
3
3
|
// what A2UI doesn't cover. Nothing in it is code from the app it came from:
|
|
4
4
|
// screens are A2UI components, the look is DTCG design tokens, rules are CEL
|
|
5
5
|
// expressions, saved data is described by a JSON Schema. Strict throughout, so a
|
|
6
|
-
// field the spec doesn't define (where code could hide) is refused.
|
|
6
|
+
// field the spec doesn't define (where code could hide) is refused. The one
|
|
7
|
+
// place code belongs is a coded component's, run sandboxed, said so where the
|
|
8
|
+
// app is installed.
|
|
7
9
|
/** A neutral id: lower-case words, dots, dashes, underscores. Partner symbol names live only in provenance. */
|
|
8
10
|
export const Id = z.string().regex(/^[a-z][a-zA-Z0-9_.-]{0,63}$/, "an id: a letter, then letters, digits, . _ or -, at most 64");
|
|
9
11
|
/** A CEL identifier (constants and parameters): letters, digits and underscores. */
|
|
@@ -42,6 +44,29 @@ export const Tokens = z.lazy(() => z
|
|
|
42
44
|
}));
|
|
43
45
|
/** A CEL expression (cel.dev); parsed and checked by the logic validator. */
|
|
44
46
|
const Cel = z.string().min(1).max(4000);
|
|
47
|
+
/** The kinds of value a coded component is given or sends: JSON's, by name. */
|
|
48
|
+
export const CODED_TYPES = ["number", "string", "boolean", "list", "map"];
|
|
49
|
+
const CodedType = z.enum(CODED_TYPES);
|
|
50
|
+
/**
|
|
51
|
+
* A coded component: what the catalog can't draw, as the spec's own code, run
|
|
52
|
+
* sandboxed in a frame with a kit (`game`: a loop, a canvas, touch and keys).
|
|
53
|
+
* It's given only its declared inputs and may only send its declared events;
|
|
54
|
+
* a screen places it with the catalog's Coded component.
|
|
55
|
+
*/
|
|
56
|
+
export const CodedComponent = z
|
|
57
|
+
.object({
|
|
58
|
+
kind: z.literal("coded"),
|
|
59
|
+
kit: z.enum(["game"]),
|
|
60
|
+
inputs: z.record(CelName, CodedType).default({}),
|
|
61
|
+
events: z.record(CelName, z.record(CelName, CodedType)).default({}),
|
|
62
|
+
code: z
|
|
63
|
+
.string()
|
|
64
|
+
.min(1)
|
|
65
|
+
.max(64 * 1024)
|
|
66
|
+
.refine((c) => !/<\/script/i.test(c), "can't contain </script>, which would end the frame's script"),
|
|
67
|
+
})
|
|
68
|
+
.strict()
|
|
69
|
+
.refine((c) => Object.keys(c.inputs).length <= 10 && Object.keys(c.events).length <= 10, "at most 10 inputs and 10 events");
|
|
45
70
|
export const SpecSchema = z
|
|
46
71
|
.object({
|
|
47
72
|
v: z.literal(1),
|
|
@@ -128,6 +153,11 @@ export const SpecSchema = z
|
|
|
128
153
|
promises: z.array(z.object({ id: z.string().regex(/^P-\d+$/), text: Text(400), kind: z.literal("data").optional() }).strict()).default([]),
|
|
129
154
|
/** CloudCrane's test steps, as its blueprint tests take them; CloudCrane checks their shape. */
|
|
130
155
|
tests: z.array(z.record(z.unknown())).max(30).default([]),
|
|
156
|
+
/** Coded components, by name: the spec's own code for what the catalog can't draw. */
|
|
157
|
+
components: z
|
|
158
|
+
.record(Id, CodedComponent)
|
|
159
|
+
.refine((c) => Object.keys(c).length <= 5, "at most 5 components")
|
|
160
|
+
.default({}),
|
|
131
161
|
/** What a remix may change and what stays as the partner made it. */
|
|
132
162
|
mods: z
|
|
133
163
|
.object({ open: z.array(PointerPattern), locked: z.array(PointerPattern) })
|
package/dist/src/validate.js
CHANGED
|
@@ -46,9 +46,38 @@ export function validateSpec(input, catalog) {
|
|
|
46
46
|
for (const m of spec.data.migrations)
|
|
47
47
|
if (m.to !== m.from + 1 || m.to > spec.data.version)
|
|
48
48
|
problems.push({ at: `data.migrations ${m.from}`, problem: "steps go one version at a time, up to data.version" });
|
|
49
|
-
problems.push(...logicProblems(spec), ...leakProblems(spec));
|
|
49
|
+
problems.push(...codedProblems(spec), ...logicProblems(spec), ...leakProblems(spec));
|
|
50
50
|
for (const locked of spec.mods.locked)
|
|
51
51
|
if (spec.mods.open.includes(locked))
|
|
52
52
|
problems.push({ at: `mods ${locked}`, problem: "both open and locked" });
|
|
53
53
|
return { spec: problems.length ? null : spec, problems };
|
|
54
54
|
}
|
|
55
|
+
/** Each Coded placement names a component the spec defines, and only its declared inputs and events; each component is placed. */
|
|
56
|
+
function codedProblems(spec) {
|
|
57
|
+
const problems = [];
|
|
58
|
+
const placed = new Set();
|
|
59
|
+
for (const m of spec.surface.messages) {
|
|
60
|
+
const comps = m.updateComponents?.components ?? [];
|
|
61
|
+
for (const c of comps) {
|
|
62
|
+
if (c.component !== "Coded" || typeof c.use !== "string")
|
|
63
|
+
continue;
|
|
64
|
+
const at = `surface ${c.id}`;
|
|
65
|
+
const def = spec.components[c.use];
|
|
66
|
+
if (!def) {
|
|
67
|
+
problems.push({ at, problem: `no coded component "${c.use}" in components` });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
placed.add(c.use);
|
|
71
|
+
for (const i of c.inputs ?? [])
|
|
72
|
+
if (!(i.name in def.inputs))
|
|
73
|
+
problems.push({ at, problem: `${c.use} declares no input "${i.name}"` });
|
|
74
|
+
for (const e of c.on ?? [])
|
|
75
|
+
if (!(e.name in def.events))
|
|
76
|
+
problems.push({ at, problem: `${c.use} declares no event "${e.name}"` });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const name of Object.keys(spec.components))
|
|
80
|
+
if (!placed.has(name))
|
|
81
|
+
problems.push({ at: `components.${name}`, problem: "no screen places it" });
|
|
82
|
+
return problems;
|
|
83
|
+
}
|
package/package.json
CHANGED
package/schema/app-spec.v1.json
CHANGED
|
@@ -416,6 +416,79 @@
|
|
|
416
416
|
"maxItems": 30,
|
|
417
417
|
"default": []
|
|
418
418
|
},
|
|
419
|
+
"components": {
|
|
420
|
+
"type": "object",
|
|
421
|
+
"additionalProperties": {
|
|
422
|
+
"type": "object",
|
|
423
|
+
"properties": {
|
|
424
|
+
"kind": {
|
|
425
|
+
"type": "string",
|
|
426
|
+
"const": "coded"
|
|
427
|
+
},
|
|
428
|
+
"kit": {
|
|
429
|
+
"type": "string",
|
|
430
|
+
"enum": [
|
|
431
|
+
"game"
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
"inputs": {
|
|
435
|
+
"type": "object",
|
|
436
|
+
"additionalProperties": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"enum": [
|
|
439
|
+
"number",
|
|
440
|
+
"string",
|
|
441
|
+
"boolean",
|
|
442
|
+
"list",
|
|
443
|
+
"map"
|
|
444
|
+
]
|
|
445
|
+
},
|
|
446
|
+
"propertyNames": {
|
|
447
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
|
|
448
|
+
},
|
|
449
|
+
"default": {}
|
|
450
|
+
},
|
|
451
|
+
"events": {
|
|
452
|
+
"type": "object",
|
|
453
|
+
"additionalProperties": {
|
|
454
|
+
"type": "object",
|
|
455
|
+
"additionalProperties": {
|
|
456
|
+
"type": "string",
|
|
457
|
+
"enum": [
|
|
458
|
+
"number",
|
|
459
|
+
"string",
|
|
460
|
+
"boolean",
|
|
461
|
+
"list",
|
|
462
|
+
"map"
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
"propertyNames": {
|
|
466
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
"propertyNames": {
|
|
470
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
|
|
471
|
+
},
|
|
472
|
+
"default": {}
|
|
473
|
+
},
|
|
474
|
+
"code": {
|
|
475
|
+
"type": "string",
|
|
476
|
+
"minLength": 1,
|
|
477
|
+
"maxLength": 65536
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"required": [
|
|
481
|
+
"kind",
|
|
482
|
+
"kit",
|
|
483
|
+
"code"
|
|
484
|
+
],
|
|
485
|
+
"additionalProperties": false
|
|
486
|
+
},
|
|
487
|
+
"propertyNames": {
|
|
488
|
+
"pattern": "^[a-z][a-zA-Z0-9_.-]{0,63}$"
|
|
489
|
+
},
|
|
490
|
+
"default": {}
|
|
491
|
+
},
|
|
419
492
|
"mods": {
|
|
420
493
|
"type": "object",
|
|
421
494
|
"properties": {
|