@fungi.computer/braid 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +260 -2
- package/dist/cloudflare/graph-engine.d.ts +25 -0
- package/dist/cloudflare/graph-engine.js +46 -0
- package/dist/cloudflare/graph-program.d.ts +6 -0
- package/dist/cloudflare/graph-program.js +528 -0
- package/dist/cloudflare/index.d.ts +1 -0
- package/dist/cloudflare/index.js +1 -0
- package/dist/code-execution.d.ts +16 -0
- package/dist/code-execution.js +50 -0
- package/dist/code-result.d.ts +11 -0
- package/dist/code-result.js +19 -0
- package/dist/code-sandbox.d.ts +9 -0
- package/dist/code-sandbox.js +13 -0
- package/dist/compile.d.ts +41 -0
- package/dist/compile.js +240 -0
- package/dist/execution.d.ts +113 -0
- package/dist/execution.js +108 -0
- package/dist/expressions.d.ts +16 -0
- package/dist/expressions.js +322 -0
- package/dist/git-access.d.ts +59 -0
- package/dist/git-access.js +106 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/ports.d.ts +58 -0
- package/dist/ports.js +256 -0
- package/dist/runtime/arming-authority.d.ts +83 -0
- package/dist/runtime/arming-authority.js +90 -0
- package/dist/runtime/bindings.d.ts +25 -0
- package/dist/runtime/bindings.js +41 -0
- package/dist/runtime/capture.d.ts +18 -0
- package/dist/runtime/capture.js +99 -0
- package/dist/runtime/git-pass-contract.d.ts +63 -0
- package/dist/runtime/git-pass-contract.js +36 -0
- package/dist/runtime/git-pass-store.d.ts +32 -0
- package/dist/runtime/git-pass-store.js +82 -0
- package/dist/runtime/git-pass.d.ts +41 -0
- package/dist/runtime/git-pass.js +72 -0
- package/dist/runtime/graph-contract.d.ts +784 -0
- package/dist/runtime/graph-contract.js +122 -0
- package/dist/runtime/graph-dispatch.d.ts +31 -0
- package/dist/runtime/graph-dispatch.js +111 -0
- package/dist/runtime/graph-run-store.d.ts +73 -0
- package/dist/runtime/graph-run-store.js +267 -0
- package/dist/runtime/graph-start.d.ts +90 -0
- package/dist/runtime/graph-start.js +124 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +20 -0
- package/dist/runtime/ports.d.ts +153 -0
- package/dist/runtime/ports.js +1 -0
- package/dist/runtime/run-authority.d.ts +60 -0
- package/dist/runtime/run-authority.js +145 -0
- package/dist/runtime/run-ceiling.d.ts +106 -0
- package/dist/runtime/run-ceiling.js +23 -0
- package/dist/runtime/run-member.d.ts +3 -0
- package/dist/runtime/run-member.js +12 -0
- package/dist/runtime/run-owner.d.ts +1628 -0
- package/dist/runtime/run-owner.js +513 -0
- package/dist/runtime/run-report.d.ts +14 -0
- package/dist/runtime/run-report.js +23 -0
- package/dist/runtime/schedules.d.ts +39 -0
- package/dist/runtime/schedules.js +149 -0
- package/dist/runtime/stored-trigger-rows.d.ts +12 -0
- package/dist/runtime/stored-trigger-rows.js +17 -0
- package/dist/runtime/trigger-owner.d.ts +590 -0
- package/dist/runtime/trigger-owner.js +280 -0
- package/dist/runtime/trigger-plans.d.ts +766 -0
- package/dist/runtime/trigger-plans.js +62 -0
- package/dist/runtime/trigger-router.d.ts +34 -0
- package/dist/runtime/trigger-router.js +143 -0
- package/dist/runtime/trigger-run-reservations.d.ts +28 -0
- package/dist/runtime/trigger-run-reservations.js +114 -0
- package/dist/schema.d.ts +1111 -0
- package/dist/schema.js +227 -0
- package/dist/triggers.d.ts +46 -0
- package/dist/triggers.js +75 -0
- package/dist/validate.d.ts +25 -0
- package/dist/validate.js +679 -0
- package/dist/workflow-id.d.ts +3 -0
- package/dist/workflow-id.js +3 -0
- package/package.json +95 -3
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import Mustache from "mustache";
|
|
2
|
+
import pointer from "jsonpointer";
|
|
3
|
+
import * as S from "effect/Schema";
|
|
4
|
+
import { portProjection } from "./ports.js";
|
|
5
|
+
/** Expected syntax refusal only; defects in a parser or graph traversal escape. */
|
|
6
|
+
export class ExpressionSyntaxError extends Error {
|
|
7
|
+
}
|
|
8
|
+
/** Mustache parses the representation; only its interpolation tokens belong to v1. */
|
|
9
|
+
export function templateRefs(template) {
|
|
10
|
+
let tokens;
|
|
11
|
+
try {
|
|
12
|
+
tokens = Mustache.parse(template, ["{{", "}}"]);
|
|
13
|
+
}
|
|
14
|
+
catch (cause) {
|
|
15
|
+
// For a string and these fixed tags, Mustache 4's syntax refusals are plain
|
|
16
|
+
// Error. Native defects (TypeError, RangeError, etc.) are not syntax issues.
|
|
17
|
+
if (!(cause instanceof Error) || cause.constructor !== Error)
|
|
18
|
+
throw cause;
|
|
19
|
+
throw new ExpressionSyntaxError("Invalid Mustache syntax", { cause });
|
|
20
|
+
}
|
|
21
|
+
return tokens.flatMap((token) => {
|
|
22
|
+
if (token[0] === "text")
|
|
23
|
+
return [];
|
|
24
|
+
if (token[0] !== "name" ||
|
|
25
|
+
!/^([$]input|[$]item|[A-Za-z][A-Za-z0-9_-]*)[.][A-Za-z][A-Za-z0-9_-]*$/.test(token[1]))
|
|
26
|
+
throw new ExpressionSyntaxError("Only node.port interpolation is supported");
|
|
27
|
+
const [node, port] = token[1].split(".");
|
|
28
|
+
if (!node || !port)
|
|
29
|
+
throw new ExpressionSyntaxError("Invalid template reference");
|
|
30
|
+
return [{ node, port }];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function mappingRefs(mapping) {
|
|
34
|
+
if ("from" in mapping) {
|
|
35
|
+
if (mapping.path !== undefined) {
|
|
36
|
+
try {
|
|
37
|
+
pointer.compile(mapping.path);
|
|
38
|
+
}
|
|
39
|
+
catch (cause) {
|
|
40
|
+
// jsonpointer's string compile interface reports invalid syntax as
|
|
41
|
+
// plain Error; do not turn native error subtypes into author refusals.
|
|
42
|
+
if (!(cause instanceof Error) || cause.constructor !== Error)
|
|
43
|
+
throw cause;
|
|
44
|
+
throw new ExpressionSyntaxError("Invalid JSON pointer syntax", {
|
|
45
|
+
cause,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return [mapping.from];
|
|
50
|
+
}
|
|
51
|
+
if ("template" in mapping)
|
|
52
|
+
return templateRefs(mapping.template);
|
|
53
|
+
if ("coalesce" in mapping)
|
|
54
|
+
return mapping.coalesce.flatMap(mappingRefs);
|
|
55
|
+
if ("object" in mapping)
|
|
56
|
+
return Object.values(mapping.object).flatMap(mappingRefs);
|
|
57
|
+
if ("array" in mapping)
|
|
58
|
+
return mapping.array.flatMap(mappingRefs);
|
|
59
|
+
if ("each" in mapping)
|
|
60
|
+
return [
|
|
61
|
+
...mappingRefs(mapping.each.over),
|
|
62
|
+
...mappingRefs(mapping.each.body).filter((ref) => {
|
|
63
|
+
if (ref.node !== "$item")
|
|
64
|
+
return true;
|
|
65
|
+
if (ref.port !== "value" && ref.port !== "index")
|
|
66
|
+
throw new ExpressionSyntaxError("Each exposes only $item.value and $item.index");
|
|
67
|
+
return false;
|
|
68
|
+
}),
|
|
69
|
+
];
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
export function predicateRefs(test) {
|
|
73
|
+
if ("tests" in test)
|
|
74
|
+
return test.tests.flatMap(predicateRefs);
|
|
75
|
+
if ("test" in test)
|
|
76
|
+
return predicateRefs(test.test);
|
|
77
|
+
if ("value" in test)
|
|
78
|
+
return mappingRefs(test.value);
|
|
79
|
+
return [...mappingRefs(test.left), ...mappingRefs(test.right)];
|
|
80
|
+
}
|
|
81
|
+
export function nodeRefs(node) {
|
|
82
|
+
switch (node.kind) {
|
|
83
|
+
case "app":
|
|
84
|
+
case "code":
|
|
85
|
+
return Object.values(node.values).flatMap(mappingRefs);
|
|
86
|
+
case "review":
|
|
87
|
+
return node.show;
|
|
88
|
+
case "map":
|
|
89
|
+
return [node.over];
|
|
90
|
+
case "repeat":
|
|
91
|
+
return [{ node: "$input", port: "item" }];
|
|
92
|
+
case "condition":
|
|
93
|
+
return predicateRefs(node.test);
|
|
94
|
+
case "transform":
|
|
95
|
+
return Object.values(node.fields).flatMap(mappingRefs);
|
|
96
|
+
case "wait":
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function readPort(values, ref) {
|
|
101
|
+
if (!Object.hasOwn(values, ref.node))
|
|
102
|
+
return undefined;
|
|
103
|
+
const node = values[ref.node];
|
|
104
|
+
return node && Object.hasOwn(node, ref.port) ? node[ref.port] : undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Infer constructed data through the same Mapping grammar used at execution. */
|
|
107
|
+
export function mappingProjection(mapping, source) {
|
|
108
|
+
const mapped = (part) => mappingProjection(part, source);
|
|
109
|
+
const union = (parts) => parts.length === 0
|
|
110
|
+
? {}
|
|
111
|
+
: parts.length === 1
|
|
112
|
+
? (parts[0] ?? {})
|
|
113
|
+
: { anyOf: [...parts] };
|
|
114
|
+
if ("from" in mapping)
|
|
115
|
+
return source(mapping.from, mapping.path) ?? {};
|
|
116
|
+
if ("literal" in mapping) {
|
|
117
|
+
const value = mapping.literal;
|
|
118
|
+
if (S.is(S.Array(S.Json))(value))
|
|
119
|
+
return {
|
|
120
|
+
type: "array",
|
|
121
|
+
const: value,
|
|
122
|
+
items: union(value.map((item) => mapped({ literal: item }))),
|
|
123
|
+
minItems: value.length,
|
|
124
|
+
maxItems: value.length,
|
|
125
|
+
};
|
|
126
|
+
if (S.is(S.JsonObject)(value))
|
|
127
|
+
return {
|
|
128
|
+
type: "object",
|
|
129
|
+
const: value,
|
|
130
|
+
properties: Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
131
|
+
key,
|
|
132
|
+
mapped({ literal: item }),
|
|
133
|
+
])),
|
|
134
|
+
required: Object.keys(value),
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
type: value === null
|
|
139
|
+
? "null"
|
|
140
|
+
: S.is(S.String)(value)
|
|
141
|
+
? "string"
|
|
142
|
+
: S.is(S.Boolean)(value)
|
|
143
|
+
? "boolean"
|
|
144
|
+
: Number.isInteger(value)
|
|
145
|
+
? "integer"
|
|
146
|
+
: "number",
|
|
147
|
+
const: value,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if ("coalesce" in mapping)
|
|
151
|
+
return union(mapping.coalesce
|
|
152
|
+
.filter((part) => !("literal" in part && part.literal === null))
|
|
153
|
+
.map(mapped));
|
|
154
|
+
if ("object" in mapping)
|
|
155
|
+
return {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: Object.fromEntries(Object.entries(mapping.object).map(([key, part]) => [
|
|
158
|
+
key,
|
|
159
|
+
mapped(part),
|
|
160
|
+
])),
|
|
161
|
+
required: Object.keys(mapping.object),
|
|
162
|
+
additionalProperties: false,
|
|
163
|
+
};
|
|
164
|
+
if ("array" in mapping)
|
|
165
|
+
return {
|
|
166
|
+
type: "array",
|
|
167
|
+
items: union(mapping.array.map(mapped)),
|
|
168
|
+
minItems: mapping.array.length,
|
|
169
|
+
maxItems: mapping.array.length,
|
|
170
|
+
};
|
|
171
|
+
if ("each" in mapping) {
|
|
172
|
+
const over = mapped(mapping.each.over);
|
|
173
|
+
const items = mappingProjection(mapping.each.body, (ref, path) => ref.node === "$item"
|
|
174
|
+
? portProjection(ref.port === "value"
|
|
175
|
+
? over.items
|
|
176
|
+
: ref.port === "index"
|
|
177
|
+
? {
|
|
178
|
+
type: "integer",
|
|
179
|
+
minimum: 0,
|
|
180
|
+
maximum: mapping.each.maxItems - 1,
|
|
181
|
+
}
|
|
182
|
+
: undefined, path)
|
|
183
|
+
: source(ref, path));
|
|
184
|
+
return over.type === "array"
|
|
185
|
+
? {
|
|
186
|
+
type: "array",
|
|
187
|
+
items,
|
|
188
|
+
minItems: over.minItems ?? 0,
|
|
189
|
+
maxItems: Math.min(over.maxItems ?? mapping.each.maxItems, mapping.each.maxItems),
|
|
190
|
+
}
|
|
191
|
+
: {};
|
|
192
|
+
}
|
|
193
|
+
const refs = templateRefs(mapping.template);
|
|
194
|
+
let minimum = 0;
|
|
195
|
+
let maximum = 0;
|
|
196
|
+
let cursor = 0;
|
|
197
|
+
for (const token of Mustache.parse(mapping.template, ["{{", "}}"])) {
|
|
198
|
+
if (token[0] === "text") {
|
|
199
|
+
minimum += token[1].length;
|
|
200
|
+
if (maximum !== undefined)
|
|
201
|
+
maximum += token[1].length;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const ref = refs[cursor++];
|
|
205
|
+
const schema = ref ? source(ref) : undefined;
|
|
206
|
+
// Templates retain their optional-reference behavior: absence renders empty text.
|
|
207
|
+
if (schema?.maxLength === undefined)
|
|
208
|
+
maximum = undefined;
|
|
209
|
+
else if (maximum !== undefined)
|
|
210
|
+
maximum += schema.maxLength;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return maximum === undefined
|
|
214
|
+
? { type: "string", minLength: minimum }
|
|
215
|
+
: { type: "string", minLength: minimum, maxLength: maximum };
|
|
216
|
+
}
|
|
217
|
+
export function evaluateMapping(mapping, values) {
|
|
218
|
+
// Materialization shares the engine's 64 KiB result ceiling. Count visits as
|
|
219
|
+
// well so nested each expressions cannot allocate an exponential intermediate.
|
|
220
|
+
let remaining = 64 * 1024;
|
|
221
|
+
const evaluate = (mapping, values) => {
|
|
222
|
+
if (--remaining < 0)
|
|
223
|
+
return undefined;
|
|
224
|
+
const value = evaluateValue(mapping, values);
|
|
225
|
+
return value !== undefined && JSON.stringify(value).length <= 64 * 1024
|
|
226
|
+
? value
|
|
227
|
+
: undefined;
|
|
228
|
+
};
|
|
229
|
+
const evaluateValue = (mapping, values) => {
|
|
230
|
+
if ("literal" in mapping)
|
|
231
|
+
return mapping.literal;
|
|
232
|
+
if ("from" in mapping) {
|
|
233
|
+
const value = readPort(values, mapping.from);
|
|
234
|
+
if (mapping.path === undefined || value === undefined)
|
|
235
|
+
return value;
|
|
236
|
+
if (!S.is(S.Union([S.JsonObject, S.Array(S.Json)]))(value))
|
|
237
|
+
return undefined;
|
|
238
|
+
const selected = pointer.get(value, mapping.path);
|
|
239
|
+
return selected === undefined
|
|
240
|
+
? undefined
|
|
241
|
+
: S.decodeUnknownSync(S.Json)(selected);
|
|
242
|
+
}
|
|
243
|
+
if ("coalesce" in mapping) {
|
|
244
|
+
for (const part of mapping.coalesce) {
|
|
245
|
+
const value = evaluate(part, values);
|
|
246
|
+
if (value !== undefined && value !== null)
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
if ("object" in mapping) {
|
|
252
|
+
const entries = [];
|
|
253
|
+
for (const [key, part] of Object.entries(mapping.object)) {
|
|
254
|
+
const value = evaluate(part, values);
|
|
255
|
+
if (value === undefined)
|
|
256
|
+
return undefined;
|
|
257
|
+
entries.push([key, value]);
|
|
258
|
+
}
|
|
259
|
+
return Object.fromEntries(entries);
|
|
260
|
+
}
|
|
261
|
+
if ("array" in mapping) {
|
|
262
|
+
const items = [];
|
|
263
|
+
for (const part of mapping.array) {
|
|
264
|
+
const value = evaluate(part, values);
|
|
265
|
+
if (value === undefined)
|
|
266
|
+
return undefined;
|
|
267
|
+
items.push(value);
|
|
268
|
+
}
|
|
269
|
+
return items;
|
|
270
|
+
}
|
|
271
|
+
if ("each" in mapping) {
|
|
272
|
+
const over = evaluate(mapping.each.over, values);
|
|
273
|
+
if (!S.is(S.Array(S.Json))(over) || over.length > mapping.each.maxItems)
|
|
274
|
+
return undefined;
|
|
275
|
+
const items = [];
|
|
276
|
+
for (const [index, value] of over.entries()) {
|
|
277
|
+
const item = evaluate(mapping.each.body, {
|
|
278
|
+
...values,
|
|
279
|
+
$item: { value, index },
|
|
280
|
+
});
|
|
281
|
+
if (item === undefined)
|
|
282
|
+
return undefined;
|
|
283
|
+
items.push(item);
|
|
284
|
+
}
|
|
285
|
+
return items;
|
|
286
|
+
}
|
|
287
|
+
templateRefs(mapping.template);
|
|
288
|
+
return Mustache.render(mapping.template, values, undefined, {
|
|
289
|
+
escape: (value) => value,
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
return evaluate(mapping, values);
|
|
293
|
+
}
|
|
294
|
+
export function evaluatePredicate(test, values) {
|
|
295
|
+
switch (test.op) {
|
|
296
|
+
case "and":
|
|
297
|
+
return test.tests.every((child) => evaluatePredicate(child, values));
|
|
298
|
+
case "or":
|
|
299
|
+
return test.tests.some((child) => evaluatePredicate(child, values));
|
|
300
|
+
case "not":
|
|
301
|
+
return !evaluatePredicate(test.test, values);
|
|
302
|
+
case "exists":
|
|
303
|
+
return evaluateMapping(test.value, values) !== undefined;
|
|
304
|
+
default: {
|
|
305
|
+
const left = evaluateMapping(test.left, values);
|
|
306
|
+
const right = evaluateMapping(test.right, values);
|
|
307
|
+
switch (test.op) {
|
|
308
|
+
case "eq":
|
|
309
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
310
|
+
case "neq":
|
|
311
|
+
return JSON.stringify(left) !== JSON.stringify(right);
|
|
312
|
+
case "gt":
|
|
313
|
+
return S.is(S.Number)(left) && S.is(S.Number)(right) && left > right;
|
|
314
|
+
case "lt":
|
|
315
|
+
return S.is(S.Number)(left) && S.is(S.Number)(right) && left < right;
|
|
316
|
+
case "in":
|
|
317
|
+
return (Array.isArray(right) &&
|
|
318
|
+
right.some((item) => JSON.stringify(item) === JSON.stringify(left)));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Frozen installation identity or explicit registered Team-source namespace. */
|
|
3
|
+
export declare const workflowBindingSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
4
|
+
appId: z.ZodString;
|
|
5
|
+
installationId: z.ZodString;
|
|
6
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
7
|
+
kind: z.ZodLiteral<"team-source">;
|
|
8
|
+
}, z.core.$strict>]>;
|
|
9
|
+
/** Installation selections are admitted through the host's catalog before compilation. */
|
|
10
|
+
export declare const bindingsSelectionSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
11
|
+
kind: z.ZodLiteral<"team-source">;
|
|
12
|
+
}, z.core.$strict>]>>;
|
|
13
|
+
export type BindingsSelection = z.output<typeof bindingsSelectionSchema>;
|
|
14
|
+
/** Published node declaration: a write branch ceiling or a read-only scope. */
|
|
15
|
+
export declare const gitAccessSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
16
|
+
binding: z.ZodString;
|
|
17
|
+
repository: z.ZodString;
|
|
18
|
+
branches: z.ZodArray<z.ZodString>;
|
|
19
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
20
|
+
binding: z.ZodString;
|
|
21
|
+
repository: z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"$trigger.repository">]>;
|
|
22
|
+
access: z.ZodLiteral<"read">;
|
|
23
|
+
}, z.core.$strict>]>;
|
|
24
|
+
/** Host-resolved scope carried to a process; data alone never grants this authority. */
|
|
25
|
+
export declare const resolvedGitAccessSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
26
|
+
repository: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
27
|
+
kind: z.ZodLiteral<"installation">;
|
|
28
|
+
installationId: z.ZodString;
|
|
29
|
+
repository: z.ZodString;
|
|
30
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
31
|
+
kind: z.ZodLiteral<"team-source">;
|
|
32
|
+
repository: z.ZodString;
|
|
33
|
+
}, z.core.$strict>], "kind">;
|
|
34
|
+
branches: z.ZodArray<z.ZodString>;
|
|
35
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
36
|
+
repository: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
37
|
+
kind: z.ZodLiteral<"installation">;
|
|
38
|
+
installationId: z.ZodString;
|
|
39
|
+
repository: z.ZodString;
|
|
40
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
41
|
+
kind: z.ZodLiteral<"team-source">;
|
|
42
|
+
repository: z.ZodString;
|
|
43
|
+
}, z.core.$strict>], "kind">;
|
|
44
|
+
access: z.ZodLiteral<"read">;
|
|
45
|
+
}, z.core.$strict>]>;
|
|
46
|
+
export type GitAccess = z.infer<typeof gitAccessSchema>;
|
|
47
|
+
export type ResolvedGitAccess = z.infer<typeof resolvedGitAccessSchema>;
|
|
48
|
+
/** The Git scope a pass for this resolved access admits. */
|
|
49
|
+
export declare function gitAccessScope(access: GitAccess | ResolvedGitAccess): "read" | "write";
|
|
50
|
+
/** Exact scope equality: same repository, same access, same ordered branches. */
|
|
51
|
+
export declare function resolvedGitAccessEqual(left: ResolvedGitAccess, right: ResolvedGitAccess): boolean;
|
|
52
|
+
/** One event-named repository, already decoded from the trigger's mapping. */
|
|
53
|
+
export declare const triggerRepositorySchema: z.ZodString;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve only the selected frozen namespace; never infer the App executing
|
|
56
|
+
* the node. `$trigger.repository` resolves only with the run's trigger
|
|
57
|
+
* repository; without one it is unavailable.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveGitAccess(bindings: Readonly<Record<string, z.output<typeof workflowBindingSchema>>>, authored: GitAccess, triggerRepository?: string): ResolvedGitAccess | null;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { repositoryRefSchema, repositoryRefsEqual, } from "@fungi.computer/agar/git";
|
|
3
|
+
const repository = z
|
|
4
|
+
.string()
|
|
5
|
+
.regex(/^[a-zA-Z0-9._-]{1,128}$/)
|
|
6
|
+
.refine((value) => value !== "." && value !== "..");
|
|
7
|
+
const bindingName = z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(80)
|
|
11
|
+
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/);
|
|
12
|
+
const identity = z.string().min(1).max(256);
|
|
13
|
+
const teamSourceBinding = z.strictObject({ kind: z.literal("team-source") });
|
|
14
|
+
/** Frozen installation identity or explicit registered Team-source namespace. */
|
|
15
|
+
export const workflowBindingSchema = z.union([
|
|
16
|
+
z.strictObject({ appId: identity, installationId: identity }),
|
|
17
|
+
teamSourceBinding,
|
|
18
|
+
]);
|
|
19
|
+
/** Installation selections are admitted through the host's catalog before compilation. */
|
|
20
|
+
export const bindingsSelectionSchema = z.record(bindingName, z.union([identity, teamSourceBinding]));
|
|
21
|
+
/** A literal branch scope, never a ref pattern or a revision expression. */
|
|
22
|
+
const branch = z
|
|
23
|
+
.string()
|
|
24
|
+
.max(1024)
|
|
25
|
+
.regex(/^refs\/heads\/[A-Za-z0-9_/-][A-Za-z0-9._/-]*$/)
|
|
26
|
+
.refine((value) => !value.includes("..") &&
|
|
27
|
+
!value.includes("//") &&
|
|
28
|
+
!value.endsWith("/") &&
|
|
29
|
+
!value.endsWith(".") &&
|
|
30
|
+
value
|
|
31
|
+
.split("/")
|
|
32
|
+
.every((part) => !part.startsWith(".") && !part.endsWith(".lock")));
|
|
33
|
+
const branches = z
|
|
34
|
+
.array(branch)
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(256)
|
|
37
|
+
.refine((values) => new Set(values).size === values.length, "Branches must be unique");
|
|
38
|
+
/** Write access names a fixed repository and its exact branch ceiling, admitted at arm time. */
|
|
39
|
+
const writeGitAccessSchema = z.strictObject({
|
|
40
|
+
binding: bindingName,
|
|
41
|
+
repository,
|
|
42
|
+
branches,
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Read access never pushes. `$trigger.repository` is the one repository the
|
|
46
|
+
* firing event names through its trigger's `repository` mapping; it resolves
|
|
47
|
+
* per run, inside the trigger App's own host-attested installation.
|
|
48
|
+
*/
|
|
49
|
+
const readGitAccessSchema = z.strictObject({
|
|
50
|
+
binding: bindingName,
|
|
51
|
+
repository: z.union([repository, z.literal("$trigger.repository")]),
|
|
52
|
+
access: z.literal("read"),
|
|
53
|
+
});
|
|
54
|
+
/** Published node declaration: a write branch ceiling or a read-only scope. */
|
|
55
|
+
export const gitAccessSchema = z.union([
|
|
56
|
+
writeGitAccessSchema,
|
|
57
|
+
readGitAccessSchema,
|
|
58
|
+
]);
|
|
59
|
+
/** Host-resolved scope carried to a process; data alone never grants this authority. */
|
|
60
|
+
export const resolvedGitAccessSchema = z.union([
|
|
61
|
+
z.strictObject({ repository: repositoryRefSchema, branches }),
|
|
62
|
+
z.strictObject({
|
|
63
|
+
repository: repositoryRefSchema,
|
|
64
|
+
access: z.literal("read"),
|
|
65
|
+
}),
|
|
66
|
+
]);
|
|
67
|
+
/** The Git scope a pass for this resolved access admits. */
|
|
68
|
+
export function gitAccessScope(access) {
|
|
69
|
+
return "access" in access ? access.access : "write";
|
|
70
|
+
}
|
|
71
|
+
/** Exact scope equality: same repository, same access, same ordered branches. */
|
|
72
|
+
export function resolvedGitAccessEqual(left, right) {
|
|
73
|
+
if (!repositoryRefsEqual(left.repository, right.repository))
|
|
74
|
+
return false;
|
|
75
|
+
if ("access" in left || "access" in right)
|
|
76
|
+
return "access" in left && "access" in right;
|
|
77
|
+
return (left.branches.length === right.branches.length &&
|
|
78
|
+
left.branches.every((branch, index) => branch === right.branches[index]));
|
|
79
|
+
}
|
|
80
|
+
/** One event-named repository, already decoded from the trigger's mapping. */
|
|
81
|
+
export const triggerRepositorySchema = repository;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve only the selected frozen namespace; never infer the App executing
|
|
84
|
+
* the node. `$trigger.repository` resolves only with the run's trigger
|
|
85
|
+
* repository; without one it is unavailable.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveGitAccess(bindings, authored, triggerRepository) {
|
|
88
|
+
const binding = bindings[authored.binding];
|
|
89
|
+
if (binding === undefined)
|
|
90
|
+
return null;
|
|
91
|
+
const name = authored.repository === "$trigger.repository"
|
|
92
|
+
? triggerRepository
|
|
93
|
+
: authored.repository;
|
|
94
|
+
if (name === undefined)
|
|
95
|
+
return null;
|
|
96
|
+
const repositoryRef = "installationId" in binding
|
|
97
|
+
? {
|
|
98
|
+
kind: "installation",
|
|
99
|
+
installationId: binding.installationId,
|
|
100
|
+
repository: name,
|
|
101
|
+
}
|
|
102
|
+
: { kind: "team-source", repository: name };
|
|
103
|
+
return resolvedGitAccessSchema.parse("access" in authored
|
|
104
|
+
? { repository: repositoryRef, access: authored.access }
|
|
105
|
+
: { repository: repositoryRef, branches: authored.branches });
|
|
106
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/ports.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import type { Graph, Json, JsonSchemaProjection } from "./schema.js";
|
|
3
|
+
/** Deliberately conservative v1 projection vocabulary. Unsupported assertions are refused. */
|
|
4
|
+
export type PortSchema = {
|
|
5
|
+
readonly type?: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null";
|
|
6
|
+
readonly format?: string;
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
readonly title?: string;
|
|
9
|
+
readonly enum?: Json[];
|
|
10
|
+
readonly const?: Json;
|
|
11
|
+
readonly default?: Json;
|
|
12
|
+
readonly pattern?: string;
|
|
13
|
+
readonly anyOf?: PortSchema[];
|
|
14
|
+
readonly oneOf?: PortSchema[];
|
|
15
|
+
readonly properties?: Readonly<Record<string, PortSchema>>;
|
|
16
|
+
readonly required?: string[];
|
|
17
|
+
readonly additionalProperties?: boolean;
|
|
18
|
+
readonly items?: PortSchema;
|
|
19
|
+
readonly minItems?: number;
|
|
20
|
+
readonly maxItems?: number;
|
|
21
|
+
readonly minLength?: number;
|
|
22
|
+
readonly maxLength?: number;
|
|
23
|
+
readonly minimum?: number;
|
|
24
|
+
readonly maximum?: number;
|
|
25
|
+
readonly exclusiveMinimum?: number;
|
|
26
|
+
readonly exclusiveMaximum?: number;
|
|
27
|
+
};
|
|
28
|
+
/** Effect owns the supported schema grammar; the Workers-safe validator owns instance validation. */
|
|
29
|
+
export declare function parseProjection(value: JsonSchemaProjection): PortSchema;
|
|
30
|
+
export declare function accepts(schema: PortSchema, value: Json): boolean;
|
|
31
|
+
export type RunInputAdmission = {
|
|
32
|
+
readonly kind: "accepted";
|
|
33
|
+
readonly input: S.JsonObject;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "invalid";
|
|
36
|
+
readonly issues: readonly {
|
|
37
|
+
readonly code: "invalid_input";
|
|
38
|
+
readonly path: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
/** Every run source uses the same supported projection and instance validator. */
|
|
43
|
+
export declare function admitRunInput(definition: Pick<Graph, "input">, raw: Json): RunInputAdmission;
|
|
44
|
+
export declare function ports(schema: PortSchema): Readonly<Record<string, PortSchema>>;
|
|
45
|
+
/** Only guaranteed object paths have a static type. The library owns Pointer spelling. */
|
|
46
|
+
export declare function portProjection(schema: PortSchema | undefined, path?: string): PortSchema | undefined;
|
|
47
|
+
/** A method receives its declared value; only the graph-facing view wraps scalars and unions. */
|
|
48
|
+
export declare function methodInputPorts(schema: PortSchema): PortSchema;
|
|
49
|
+
/** Declared error-object unions expose whole completion records on mutually exclusive outlets. */
|
|
50
|
+
export declare function methodOutputPorts(schema: PortSchema): Readonly<Record<string, PortSchema>>;
|
|
51
|
+
export declare function methodInputValue(schema: PortSchema, values: Readonly<Record<string, Json>>): Json | undefined;
|
|
52
|
+
export declare function methodOutputValues(schema: PortSchema, value: Json): Readonly<Record<string, Json>> | undefined;
|
|
53
|
+
export type PortKind = "text" | "number" | "bool" | "object" | "null" | "any" | "fungi:image" | "fungi:video" | "fungi:file" | {
|
|
54
|
+
readonly list: PortKind;
|
|
55
|
+
};
|
|
56
|
+
export declare function portKind(schema: PortSchema): PortKind;
|
|
57
|
+
/** Sound subset check, not a claim to decide arbitrary JSON Schema implication. */
|
|
58
|
+
export declare function assignable(source: PortSchema, target: PortSchema): boolean;
|