@cloudcrane/app-spec 0.0.0-stage → 0.1.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/src/a2ui.d.ts +12 -0
  4. package/dist/src/a2ui.js +89 -0
  5. package/dist/src/blueprint.d.ts +27 -0
  6. package/dist/src/blueprint.js +62 -0
  7. package/dist/src/canonical.d.ts +2 -0
  8. package/dist/src/canonical.js +8 -0
  9. package/dist/src/catalog.d.ts +7 -0
  10. package/dist/src/catalog.js +83 -0
  11. package/dist/src/index.d.ts +9 -0
  12. package/dist/src/index.js +11 -0
  13. package/dist/src/jsonschema.d.ts +2 -0
  14. package/dist/src/jsonschema.js +11 -0
  15. package/dist/src/leak.d.ts +3 -0
  16. package/dist/src/leak.js +138 -0
  17. package/dist/src/logic.d.ts +36 -0
  18. package/dist/src/logic.js +197 -0
  19. package/dist/src/schema.d.ts +418 -0
  20. package/dist/src/schema.js +137 -0
  21. package/dist/src/validate.d.ts +6 -0
  22. package/dist/src/validate.js +54 -0
  23. package/dist/vendor/a2ui/v0_9_1/catalogs/basic/catalog.json +1383 -0
  24. package/dist/vendor/a2ui/v0_9_1/common_types.json +305 -0
  25. package/dist/vendor/a2ui/v0_9_1/server_to_client.json +131 -0
  26. package/package.json +78 -3
  27. package/schema/app-spec.v1.json +460 -0
  28. package/vendor/a2ui/v0_9_1/LICENSE +227 -0
  29. package/vendor/a2ui/v0_9_1/VENDOR.json +6 -0
  30. package/vendor/a2ui/v0_9_1/catalogs/basic/catalog.json +1383 -0
  31. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_complex-layout.json +65 -0
  32. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_formatted-text.json +56 -0
  33. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_incremental.json +134 -0
  34. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_interactive-button.json +51 -0
  35. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_row-layout.json +40 -0
  36. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_simple-login-form.json +77 -0
  37. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/00_simple-text.json +27 -0
  38. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/01_flight-status.json +201 -0
  39. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/02_email-compose.json +185 -0
  40. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/03_calendar-day.json +166 -0
  41. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/04_weather-current.json +168 -0
  42. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/05_product-card.json +151 -0
  43. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/06_music-player.json +165 -0
  44. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/07_task-card.json +107 -0
  45. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/08_user-profile.json +190 -0
  46. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/09_login-form.json +214 -0
  47. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/10_notification-permission.json +105 -0
  48. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/11_purchase-complete.json +169 -0
  49. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/12_chat-message.json +144 -0
  50. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/13_coffee-order.json +253 -0
  51. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/14_sports-player.json +177 -0
  52. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/15_account-balance.json +126 -0
  53. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/16_workout-summary.json +160 -0
  54. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/17_event-detail.json +144 -0
  55. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/18_track-list.json +152 -0
  56. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/19_software-purchase.json +194 -0
  57. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/20_restaurant-card.json +140 -0
  58. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/21_shipping-status.json +137 -0
  59. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/22_credit-card.json +117 -0
  60. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/23_step-counter.json +149 -0
  61. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/24_recipe-card.json +204 -0
  62. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/25_contact-card.json +175 -0
  63. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/26_podcast-episode.json +123 -0
  64. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/27_stats-card.json +106 -0
  65. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/28_countdown-timer.json +135 -0
  66. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/29_movie-card.json +156 -0
  67. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/30_live-invitation-builder.json +205 -0
  68. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/31_incremental-dashboard.json +128 -0
  69. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/32_advanced-form-validator.json +166 -0
  70. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/33_financial-data-grid.json +171 -0
  71. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/34_child-list-template.json +80 -0
  72. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/35_markdown-text.json +44 -0
  73. package/vendor/a2ui/v0_9_1/catalogs/basic/examples/36_modal.json +65 -0
  74. package/vendor/a2ui/v0_9_1/client_capabilities.json +89 -0
  75. package/vendor/a2ui/v0_9_1/client_data_model.json +22 -0
  76. package/vendor/a2ui/v0_9_1/client_to_server.json +98 -0
  77. package/vendor/a2ui/v0_9_1/client_to_server_list.json +10 -0
  78. package/vendor/a2ui/v0_9_1/client_to_server_list_wrapper.json +14 -0
  79. package/vendor/a2ui/v0_9_1/common_types.json +305 -0
  80. package/vendor/a2ui/v0_9_1/sample.json +22 -0
  81. package/vendor/a2ui/v0_9_1/server_capabilities.json +26 -0
  82. package/vendor/a2ui/v0_9_1/server_to_client.json +132 -0
  83. package/vendor/a2ui/v0_9_1/server_to_client_list.json +10 -0
  84. package/vendor/a2ui/v0_9_1/server_to_client_list_wrapper.json +14 -0
@@ -0,0 +1,197 @@
1
+ import { Environment } from "@marcbachmann/cel-js";
2
+ import { canonical } from "./canonical.js";
3
+ export const MAX_RANGE = 3660;
4
+ const MAX_DEPTH = 64;
5
+ const DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
6
+ /** JSON in: whole numbers become CEL ints (BigInt). */
7
+ export function toCel(v) {
8
+ if (typeof v === "number" && Number.isInteger(v))
9
+ return BigInt(v);
10
+ if (Array.isArray(v))
11
+ return v.map(toCel);
12
+ if (v && typeof v === "object")
13
+ return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, toCel(x)]));
14
+ return v;
15
+ }
16
+ /** CEL out: ints become numbers again, so results are plain JSON. */
17
+ export function fromCel(v) {
18
+ if (typeof v === "bigint")
19
+ return Number(v);
20
+ if (Array.isArray(v))
21
+ return v.map(fromCel);
22
+ if (v instanceof Map)
23
+ return Object.fromEntries([...v].map(([k, x]) => [String(k), fromCel(x)]));
24
+ if (v && typeof v === "object")
25
+ return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, fromCel(x)]));
26
+ return v;
27
+ }
28
+ /** Whole days since 1970-01-01 for a real "YYYY-MM-DD", in UTC so no clock or zone can move it. */
29
+ function dayNumber(s) {
30
+ const m = DAY.exec(s);
31
+ if (!m)
32
+ throw new Error(`not a day: ${s}`);
33
+ const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
34
+ const t = Date.UTC(y, mo - 1, d);
35
+ const back = new Date(t);
36
+ if (back.getUTCFullYear() !== y || back.getUTCMonth() !== mo - 1 || back.getUTCDate() !== d)
37
+ throw new Error(`not a day: ${s}`);
38
+ return t / 86_400_000;
39
+ }
40
+ const dayString = (n) => new Date(n * 86_400_000).toISOString().slice(0, 10);
41
+ export const dates = {
42
+ addDays: (s, n) => dayString(dayNumber(s) + Number(n)),
43
+ daysBetween: (a, b) => BigInt(dayNumber(b) - dayNumber(a)),
44
+ /** The Monday that starts s's week. */
45
+ weekStart: (s) => {
46
+ const n = dayNumber(s);
47
+ return dayString(n - ((new Date(n * 86_400_000).getUTCDay() + 6) % 7));
48
+ },
49
+ dateRange: (a, b) => {
50
+ const [from, to] = [dayNumber(a), dayNumber(b)];
51
+ if (to - from > MAX_RANGE)
52
+ throw new Error(`a range of at most ${MAX_RANGE} days`);
53
+ const out = [];
54
+ for (let n = from; n < to; n++)
55
+ out.push(dayString(n));
56
+ return out;
57
+ },
58
+ };
59
+ /** Function names a CEL expression calls, by name or as a fold step ("name"). */
60
+ function callsIn(cel, names) {
61
+ const out = new Set();
62
+ for (const m of cel.matchAll(/([A-Za-z_][A-Za-z0-9_]*)\s*\(/g))
63
+ if (names.has(m[1]))
64
+ out.add(m[1]);
65
+ for (const m of cel.matchAll(/(["'])([A-Za-z_][A-Za-z0-9_]*)\1/g))
66
+ if (names.has(m[2]))
67
+ out.add(m[2]);
68
+ return [...out];
69
+ }
70
+ /** A cycle among the spec's functions, as names, or null. */
71
+ export function cycleIn(functions) {
72
+ const names = new Set(Object.keys(functions));
73
+ const edges = new Map([...names].map((n) => [n, callsIn(functions[n].cel, names)]));
74
+ const state = new Map();
75
+ const walk = (n, path) => {
76
+ if (state.get(n) === "open")
77
+ return [...path.slice(path.indexOf(n)), n];
78
+ if (state.get(n) === "done")
79
+ return null;
80
+ state.set(n, "open");
81
+ for (const m of edges.get(n) ?? []) {
82
+ const found = walk(m, [...path, n]);
83
+ if (found)
84
+ return found;
85
+ }
86
+ state.set(n, "done");
87
+ return null;
88
+ };
89
+ for (const n of names) {
90
+ const found = walk(n, []);
91
+ if (found)
92
+ return found;
93
+ }
94
+ return null;
95
+ }
96
+ /** The rules of one spec, ready to evaluate. */
97
+ export class Rules {
98
+ logic;
99
+ env;
100
+ consts;
101
+ depth = 0;
102
+ constructor(logic) {
103
+ this.logic = logic;
104
+ this.consts = toCel(logic.consts);
105
+ const env = new Environment({ unlistedVariablesAreDyn: true, homogeneousAggregateLiterals: false });
106
+ env.registerFunction("addDays(string, int): string", dates.addDays);
107
+ env.registerFunction("daysBetween(string, string): int", dates.daysBetween);
108
+ env.registerFunction("weekStart(string): string", dates.weekStart);
109
+ env.registerFunction("dateRange(string, string): list<string>", dates.dateRange);
110
+ env.registerFunction("list.sort(): list", (list) => [...list].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)));
111
+ env.registerFunction("fold(list, dyn, string): dyn", (list, init, step) => {
112
+ if (!(step in logic.functions))
113
+ throw new Error(`fold: no function ${step}`);
114
+ return list.reduce((acc, item) => this.call(step, [acc, item]), init);
115
+ });
116
+ for (const [name, fn] of Object.entries(logic.functions)) {
117
+ env.registerFunction(`${name}(${fn.params.map(() => "dyn").join(", ")}): dyn`, (...args) => this.call(name, args));
118
+ }
119
+ this.env = env;
120
+ }
121
+ call(name, args) {
122
+ const fn = this.logic.functions[name];
123
+ if (++this.depth > MAX_DEPTH) {
124
+ this.depth = 0;
125
+ throw new Error("calls nest too deep");
126
+ }
127
+ try {
128
+ return this.env.evaluate(fn.cel, { ...this.consts, ...Object.fromEntries(fn.params.map((p, i) => [p, args[i] ?? null])) });
129
+ }
130
+ finally {
131
+ this.depth--;
132
+ }
133
+ }
134
+ /** An expression's value, as JSON; null when it can't be worked out. */
135
+ evaluate(cel, vars = {}) {
136
+ try {
137
+ this.depth = 0;
138
+ return fromCel(this.env.evaluate(cel, { ...this.consts, ...toCel(vars) }));
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ /** A spec function applied to JSON arguments; null when it can't be worked out. */
145
+ apply(name, args) {
146
+ try {
147
+ this.depth = 0;
148
+ return fromCel(this.call(name, args.map(toCel)));
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ /** Why an expression won't parse, or null when it does. */
155
+ parseError(cel) {
156
+ try {
157
+ this.env.parse(cel);
158
+ return null;
159
+ }
160
+ catch (e) {
161
+ return e.message.split("\n")[0] ?? "doesn't parse";
162
+ }
163
+ }
164
+ }
165
+ /** Equal as JSON values, whatever the key order (RFC 8785 canonical form). */
166
+ const same = (a, b) => canonical(a ?? null) === canonical(b ?? null);
167
+ /** A key as one JSON Pointer token (RFC 6901). */
168
+ const token = (k) => k.replace(/~/g, "~0").replace(/\//g, "~1");
169
+ /** Problems with a spec's rules, each at its JSON Pointer: expressions that don't parse, a call cycle, vectors the rules don't reproduce. */
170
+ export function logicProblems(spec) {
171
+ const problems = [];
172
+ const cycle = cycleIn(spec.logic.functions);
173
+ if (cycle)
174
+ return [{ at: "/logic/functions", problem: `functions call each other in a cycle: ${cycle.join(" → ")}` }];
175
+ const rules = new Rules(spec.logic);
176
+ const each = [
177
+ ...Object.entries(spec.logic.functions).map(([n, f]) => [`/logic/functions/${token(n)}/cel`, f.cel]),
178
+ ...Object.entries(spec.logic.computed).map(([p, cel]) => [`/logic/computed/${token(p)}`, cel]),
179
+ ...spec.data.migrations.map((m, i) => [`/data/migrations/${i}/cel`, m.cel]),
180
+ ];
181
+ for (const [at, cel] of each) {
182
+ const why = rules.parseError(cel);
183
+ if (why)
184
+ problems.push({ at, problem: `doesn't parse: ${why}` });
185
+ }
186
+ if (problems.length)
187
+ return problems;
188
+ spec.logic.vectors.forEach((v, i) => {
189
+ const got = rules.apply(v.fn, v.in);
190
+ if (!same(got, v.out))
191
+ problems.push({
192
+ at: `/logic/vectors/${i}`,
193
+ problem: `${v.fn}(${JSON.stringify(v.in).slice(1, -1)}) gives ${JSON.stringify(got)}, not ${JSON.stringify(v.out)}`,
194
+ });
195
+ });
196
+ return problems;
197
+ }
@@ -0,0 +1,418 @@
1
+ import { z } from "zod";
2
+ /** A neutral id: lower-case words, dots, dashes, underscores. Partner symbol names live only in provenance. */
3
+ export declare const Id: z.ZodString;
4
+ /** A CEL identifier (constants and parameters): letters, digits and underscores. */
5
+ export declare const CelName: z.ZodString;
6
+ /** An RFC 6901 JSON Pointer. */
7
+ export declare const Pointer: z.ZodString;
8
+ /** A pointer, or a pointer ending in /* for everything under it (mod points). */
9
+ export declare const PointerPattern: z.ZodString;
10
+ export type TokenNode = {
11
+ $value?: unknown;
12
+ $type?: string;
13
+ $description?: string;
14
+ [name: string]: unknown;
15
+ };
16
+ export declare const Tokens: z.ZodType<TokenNode>;
17
+ export declare const SpecSchema: z.ZodObject<{
18
+ v: z.ZodLiteral<1>;
19
+ manifest: z.ZodObject<{
20
+ runtime: z.ZodString;
21
+ generator: z.ZodString;
22
+ source: z.ZodObject<{
23
+ fingerprint: z.ZodString;
24
+ }, "strict", z.ZodTypeAny, {
25
+ fingerprint: string;
26
+ }, {
27
+ fingerprint: string;
28
+ }>;
29
+ }, "strict", z.ZodTypeAny, {
30
+ runtime: string;
31
+ generator: string;
32
+ source: {
33
+ fingerprint: string;
34
+ };
35
+ }, {
36
+ runtime: string;
37
+ generator: string;
38
+ source: {
39
+ fingerprint: string;
40
+ };
41
+ }>;
42
+ module: z.ZodObject<{
43
+ id: z.ZodString;
44
+ name: z.ZodString;
45
+ purpose: z.ZodString;
46
+ /** How CloudCrane lists it: one of its icon names, its category, up to five tags (CloudCrane checks them). */
47
+ icon: z.ZodOptional<z.ZodString>;
48
+ category: z.ZodOptional<z.ZodString>;
49
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
50
+ }, "strict", z.ZodTypeAny, {
51
+ name: string;
52
+ id: string;
53
+ purpose: string;
54
+ icon?: string | undefined;
55
+ category?: string | undefined;
56
+ tags?: string[] | undefined;
57
+ }, {
58
+ name: string;
59
+ id: string;
60
+ purpose: string;
61
+ icon?: string | undefined;
62
+ category?: string | undefined;
63
+ tags?: string[] | undefined;
64
+ }>;
65
+ tokens: z.ZodType<TokenNode, z.ZodTypeDef, TokenNode>;
66
+ /** A2UI v0.9.1 messages, the way A2UI's own samples hold them; checked by surfaceProblems. */
67
+ surface: z.ZodObject<{
68
+ catalog: z.ZodString;
69
+ messages: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
70
+ }, "strict", z.ZodTypeAny, {
71
+ catalog: string;
72
+ messages: Record<string, unknown>[];
73
+ }, {
74
+ catalog: string;
75
+ messages: Record<string, unknown>[];
76
+ }>;
77
+ data: z.ZodObject<{
78
+ version: z.ZodNumber;
79
+ /** Where it's saved on the phone. */
80
+ key: z.ZodString;
81
+ /** The part of the A2UI data model that's saved; the rest is screen state. */
82
+ persist: z.ZodString;
83
+ /** JSON Schema of the saved data. */
84
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
85
+ initial: z.ZodUnknown;
86
+ /**
87
+ * Two-way views: the runtime keeps `path` equal to the data at `source` (a
88
+ * pointer with A2UI's ${/path} interpolation, like /saved/days/${/ui/selected}),
89
+ * and writes edits made through the view back there. For lists a screen shows
90
+ * by a choice the person makes, since A2UI templates take a fixed path.
91
+ */
92
+ views: z.ZodDefault<z.ZodArray<z.ZodObject<{
93
+ path: z.ZodString;
94
+ source: z.ZodString;
95
+ }, "strict", z.ZodTypeAny, {
96
+ path: string;
97
+ source: string;
98
+ }, {
99
+ path: string;
100
+ source: string;
101
+ }>, "many">>;
102
+ /** Each step turns version `from` into `to`, as a CEL expression over `saved`. */
103
+ migrations: z.ZodArray<z.ZodObject<{
104
+ from: z.ZodNumber;
105
+ to: z.ZodNumber;
106
+ cel: z.ZodString;
107
+ }, "strict", z.ZodTypeAny, {
108
+ from: number;
109
+ to: number;
110
+ cel: string;
111
+ }, {
112
+ from: number;
113
+ to: number;
114
+ cel: string;
115
+ }>, "many">;
116
+ }, "strict", z.ZodTypeAny, {
117
+ version: number;
118
+ key: string;
119
+ persist: string;
120
+ schema: Record<string, unknown>;
121
+ views: {
122
+ path: string;
123
+ source: string;
124
+ }[];
125
+ migrations: {
126
+ from: number;
127
+ to: number;
128
+ cel: string;
129
+ }[];
130
+ initial?: unknown;
131
+ }, {
132
+ version: number;
133
+ key: string;
134
+ persist: string;
135
+ schema: Record<string, unknown>;
136
+ migrations: {
137
+ from: number;
138
+ to: number;
139
+ cel: string;
140
+ }[];
141
+ initial?: unknown;
142
+ views?: {
143
+ path: string;
144
+ source: string;
145
+ }[] | undefined;
146
+ }>;
147
+ logic: z.ZodObject<{
148
+ consts: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodNumber, z.ZodString, z.ZodBoolean]>>>;
149
+ functions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
150
+ params: z.ZodArray<z.ZodString, "many">;
151
+ cel: z.ZodString;
152
+ }, "strict", z.ZodTypeAny, {
153
+ params: string[];
154
+ cel: string;
155
+ }, {
156
+ params: string[];
157
+ cel: string;
158
+ }>>>;
159
+ /** Values the screens read, kept up to date: a data-model pointer and the CEL that computes it. */
160
+ computed: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
161
+ /** Examples each function must reproduce: recorded from the source's own tests, or given. */
162
+ vectors: z.ZodDefault<z.ZodArray<z.ZodObject<{
163
+ fn: z.ZodString;
164
+ in: z.ZodArray<z.ZodUnknown, "many">;
165
+ out: z.ZodUnknown;
166
+ from: z.ZodEnum<["recorded", "hint"]>;
167
+ }, "strict", z.ZodTypeAny, {
168
+ from: "recorded" | "hint";
169
+ fn: string;
170
+ in: unknown[];
171
+ out?: unknown;
172
+ }, {
173
+ from: "recorded" | "hint";
174
+ fn: string;
175
+ in: unknown[];
176
+ out?: unknown;
177
+ }>, "many">>;
178
+ }, "strict", z.ZodTypeAny, {
179
+ consts: Record<string, string | number | boolean>;
180
+ functions: Record<string, {
181
+ params: string[];
182
+ cel: string;
183
+ }>;
184
+ computed: Record<string, string>;
185
+ vectors: {
186
+ from: "recorded" | "hint";
187
+ fn: string;
188
+ in: unknown[];
189
+ out?: unknown;
190
+ }[];
191
+ }, {
192
+ consts?: Record<string, string | number | boolean> | undefined;
193
+ functions?: Record<string, {
194
+ params: string[];
195
+ cel: string;
196
+ }> | undefined;
197
+ computed?: Record<string, string> | undefined;
198
+ vectors?: {
199
+ from: "recorded" | "hint";
200
+ fn: string;
201
+ in: unknown[];
202
+ out?: unknown;
203
+ }[] | undefined;
204
+ }>;
205
+ network: z.ZodDefault<z.ZodArray<z.ZodObject<{
206
+ site: z.ZodString;
207
+ why: z.ZodString;
208
+ }, "strict", z.ZodTypeAny, {
209
+ site: string;
210
+ why: string;
211
+ }, {
212
+ site: string;
213
+ why: string;
214
+ }>, "many">>;
215
+ journeys: z.ZodArray<z.ZodObject<{
216
+ id: z.ZodString;
217
+ title: z.ZodString;
218
+ steps: z.ZodArray<z.ZodObject<{
219
+ id: z.ZodString;
220
+ text: z.ZodString;
221
+ }, "strict", z.ZodTypeAny, {
222
+ text: string;
223
+ id: string;
224
+ }, {
225
+ text: string;
226
+ id: string;
227
+ }>, "many">;
228
+ }, "strict", z.ZodTypeAny, {
229
+ title: string;
230
+ id: string;
231
+ steps: {
232
+ text: string;
233
+ id: string;
234
+ }[];
235
+ }, {
236
+ title: string;
237
+ id: string;
238
+ steps: {
239
+ text: string;
240
+ id: string;
241
+ }[];
242
+ }>, "many">;
243
+ promises: z.ZodDefault<z.ZodArray<z.ZodObject<{
244
+ id: z.ZodString;
245
+ text: z.ZodString;
246
+ kind: z.ZodOptional<z.ZodLiteral<"data">>;
247
+ }, "strict", z.ZodTypeAny, {
248
+ text: string;
249
+ id: string;
250
+ kind?: "data" | undefined;
251
+ }, {
252
+ text: string;
253
+ id: string;
254
+ kind?: "data" | undefined;
255
+ }>, "many">>;
256
+ /** CloudCrane's test steps, as its blueprint tests take them; CloudCrane checks their shape. */
257
+ tests: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
258
+ /** What a remix may change and what stays as the partner made it. */
259
+ mods: z.ZodDefault<z.ZodObject<{
260
+ open: z.ZodArray<z.ZodString, "many">;
261
+ locked: z.ZodArray<z.ZodString, "many">;
262
+ }, "strict", z.ZodTypeAny, {
263
+ open: string[];
264
+ locked: string[];
265
+ }, {
266
+ open: string[];
267
+ locked: string[];
268
+ }>>;
269
+ }, "strict", z.ZodTypeAny, {
270
+ surface: {
271
+ catalog: string;
272
+ messages: Record<string, unknown>[];
273
+ };
274
+ v: 1;
275
+ manifest: {
276
+ runtime: string;
277
+ generator: string;
278
+ source: {
279
+ fingerprint: string;
280
+ };
281
+ };
282
+ module: {
283
+ name: string;
284
+ id: string;
285
+ purpose: string;
286
+ icon?: string | undefined;
287
+ category?: string | undefined;
288
+ tags?: string[] | undefined;
289
+ };
290
+ tokens: TokenNode;
291
+ data: {
292
+ version: number;
293
+ key: string;
294
+ persist: string;
295
+ schema: Record<string, unknown>;
296
+ views: {
297
+ path: string;
298
+ source: string;
299
+ }[];
300
+ migrations: {
301
+ from: number;
302
+ to: number;
303
+ cel: string;
304
+ }[];
305
+ initial?: unknown;
306
+ };
307
+ logic: {
308
+ consts: Record<string, string | number | boolean>;
309
+ functions: Record<string, {
310
+ params: string[];
311
+ cel: string;
312
+ }>;
313
+ computed: Record<string, string>;
314
+ vectors: {
315
+ from: "recorded" | "hint";
316
+ fn: string;
317
+ in: unknown[];
318
+ out?: unknown;
319
+ }[];
320
+ };
321
+ network: {
322
+ site: string;
323
+ why: string;
324
+ }[];
325
+ journeys: {
326
+ title: string;
327
+ id: string;
328
+ steps: {
329
+ text: string;
330
+ id: string;
331
+ }[];
332
+ }[];
333
+ promises: {
334
+ text: string;
335
+ id: string;
336
+ kind?: "data" | undefined;
337
+ }[];
338
+ tests: Record<string, unknown>[];
339
+ mods: {
340
+ open: string[];
341
+ locked: string[];
342
+ };
343
+ }, {
344
+ surface: {
345
+ catalog: string;
346
+ messages: Record<string, unknown>[];
347
+ };
348
+ v: 1;
349
+ manifest: {
350
+ runtime: string;
351
+ generator: string;
352
+ source: {
353
+ fingerprint: string;
354
+ };
355
+ };
356
+ module: {
357
+ name: string;
358
+ id: string;
359
+ purpose: string;
360
+ icon?: string | undefined;
361
+ category?: string | undefined;
362
+ tags?: string[] | undefined;
363
+ };
364
+ tokens: TokenNode;
365
+ data: {
366
+ version: number;
367
+ key: string;
368
+ persist: string;
369
+ schema: Record<string, unknown>;
370
+ migrations: {
371
+ from: number;
372
+ to: number;
373
+ cel: string;
374
+ }[];
375
+ initial?: unknown;
376
+ views?: {
377
+ path: string;
378
+ source: string;
379
+ }[] | undefined;
380
+ };
381
+ logic: {
382
+ consts?: Record<string, string | number | boolean> | undefined;
383
+ functions?: Record<string, {
384
+ params: string[];
385
+ cel: string;
386
+ }> | undefined;
387
+ computed?: Record<string, string> | undefined;
388
+ vectors?: {
389
+ from: "recorded" | "hint";
390
+ fn: string;
391
+ in: unknown[];
392
+ out?: unknown;
393
+ }[] | undefined;
394
+ };
395
+ journeys: {
396
+ title: string;
397
+ id: string;
398
+ steps: {
399
+ text: string;
400
+ id: string;
401
+ }[];
402
+ }[];
403
+ network?: {
404
+ site: string;
405
+ why: string;
406
+ }[] | undefined;
407
+ promises?: {
408
+ text: string;
409
+ id: string;
410
+ kind?: "data" | undefined;
411
+ }[] | undefined;
412
+ tests?: Record<string, unknown>[] | undefined;
413
+ mods?: {
414
+ open: string[];
415
+ locked: string[];
416
+ } | undefined;
417
+ }>;
418
+ export type Spec = z.infer<typeof SpecSchema>;