@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
package/dist/schema.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import { gitAccessSchema, workflowBindingSchema, } from "./git-access.js";
|
|
3
|
+
const boundedString = (max) => S.String.pipe(S.check(S.isMaxLength(max)));
|
|
4
|
+
const name = boundedString(80).pipe(S.check(S.isPattern(/^[A-Za-z][A-Za-z0-9_-]*$/)));
|
|
5
|
+
const count = (min, max) => S.Number.pipe(S.check(S.isInt()), S.check(S.isBetween({ minimum: min, maximum: max })));
|
|
6
|
+
const array = (schema, max) => S.Array(schema).pipe(S.check(S.isMaxLength(max)));
|
|
7
|
+
const record = (schema) => S.Record(name, schema);
|
|
8
|
+
const json = S.Json.pipe(S.check(S.makeFilter((value) => JSON.stringify(value).length <= 20_000)));
|
|
9
|
+
/** Same byte/depth envelope as the App API projection. */
|
|
10
|
+
export const projectionSchema = S.JsonObject.pipe(S.check(S.makeFilter((value) => {
|
|
11
|
+
const withinDepth = (item, depth) => {
|
|
12
|
+
if (depth > 16)
|
|
13
|
+
return false;
|
|
14
|
+
if (S.is(S.Array(S.Json))(item))
|
|
15
|
+
return item.every((child) => withinDepth(child, depth + 1));
|
|
16
|
+
if (S.is(S.JsonObject)(item))
|
|
17
|
+
return Object.values(item).every((child) => withinDepth(child, depth + 1));
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
return (new TextEncoder().encode(JSON.stringify(value)).length <= 20_000 &&
|
|
21
|
+
withinDepth(value, 0));
|
|
22
|
+
})));
|
|
23
|
+
export const portRefSchema = S.Struct({
|
|
24
|
+
node: S.Union([name, S.Literals(["$input", "$item"])]),
|
|
25
|
+
port: name,
|
|
26
|
+
});
|
|
27
|
+
const templateSchema = S.Struct({ template: boundedString(4000) });
|
|
28
|
+
const literalSchema = S.Struct({ literal: json });
|
|
29
|
+
export const valueSchema = S.Union([literalSchema, templateSchema]);
|
|
30
|
+
const fromSchema = S.Struct({
|
|
31
|
+
from: portRefSchema,
|
|
32
|
+
path: S.optionalKey(boundedString(512)),
|
|
33
|
+
});
|
|
34
|
+
export const mappingSchema = S.Union([
|
|
35
|
+
fromSchema,
|
|
36
|
+
valueSchema,
|
|
37
|
+
S.Struct({ coalesce: S.suspend(() => array(mappingSchema, 16)) }),
|
|
38
|
+
S.Struct({ object: S.suspend(() => S.Record(S.String, mappingSchema)) }),
|
|
39
|
+
S.Struct({ array: S.suspend(() => array(mappingSchema, 50)) }),
|
|
40
|
+
S.Struct({
|
|
41
|
+
each: S.Struct({
|
|
42
|
+
over: S.suspend(() => mappingSchema),
|
|
43
|
+
body: S.suspend(() => mappingSchema),
|
|
44
|
+
maxItems: count(1, 50),
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
]);
|
|
48
|
+
export const predicateSchema = S.Union([
|
|
49
|
+
S.Struct({
|
|
50
|
+
op: S.Literals(["eq", "neq", "gt", "lt", "in"]),
|
|
51
|
+
left: mappingSchema,
|
|
52
|
+
right: mappingSchema,
|
|
53
|
+
}),
|
|
54
|
+
S.Struct({ op: S.Literal("exists"), value: mappingSchema }),
|
|
55
|
+
S.Struct({
|
|
56
|
+
op: S.Literals(["and", "or"]),
|
|
57
|
+
tests: S.suspend(() => array(predicateSchema, 16)),
|
|
58
|
+
}),
|
|
59
|
+
S.Struct({ op: S.Literal("not"), test: S.suspend(() => predicateSchema) }),
|
|
60
|
+
]);
|
|
61
|
+
const duration = count(1, 7 * 24 * 60 * 60 * 1000);
|
|
62
|
+
/** JavaScript body and declared data only; the whole graph also bounds UTF-8 bytes. */
|
|
63
|
+
export const codeNodeSchema = S.Struct({
|
|
64
|
+
id: name,
|
|
65
|
+
kind: S.Literal("code"),
|
|
66
|
+
source: boundedString(256_000),
|
|
67
|
+
input: projectionSchema,
|
|
68
|
+
output: projectionSchema,
|
|
69
|
+
values: record(mappingSchema),
|
|
70
|
+
});
|
|
71
|
+
export const nodeSchema = S.Union([
|
|
72
|
+
codeNodeSchema,
|
|
73
|
+
S.Struct({
|
|
74
|
+
id: name,
|
|
75
|
+
kind: S.Literal("app"),
|
|
76
|
+
app: name,
|
|
77
|
+
method: boundedString(80),
|
|
78
|
+
methodDigest: boundedString(128),
|
|
79
|
+
values: record(mappingSchema),
|
|
80
|
+
gitAccess: S.optionalKey(S.declare((value) => gitAccessSchema.safeParse(value).success)),
|
|
81
|
+
retry: S.optionalKey(S.Struct({ attempts: count(1, 5) })),
|
|
82
|
+
}),
|
|
83
|
+
S.Struct({
|
|
84
|
+
id: name,
|
|
85
|
+
kind: S.Literal("review"),
|
|
86
|
+
title: boundedString(120),
|
|
87
|
+
show: array(portRefSchema, 64),
|
|
88
|
+
reviewers: S.Literals(["runner", "owners"]),
|
|
89
|
+
timeout: duration,
|
|
90
|
+
/**
|
|
91
|
+
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
92
|
+
* the shown item. Without choices the review approves or rejects.
|
|
93
|
+
*/
|
|
94
|
+
choices: S.optionalKey(array(S.Struct({ id: name, label: boundedString(80) }), 8)),
|
|
95
|
+
}),
|
|
96
|
+
S.Struct({ id: name, kind: S.Literal("condition"), test: predicateSchema }),
|
|
97
|
+
S.Struct({
|
|
98
|
+
id: name,
|
|
99
|
+
kind: S.Literal("map"),
|
|
100
|
+
over: portRefSchema,
|
|
101
|
+
body: name,
|
|
102
|
+
concurrency: count(1, 8),
|
|
103
|
+
maxItems: count(1, 50),
|
|
104
|
+
}),
|
|
105
|
+
S.Struct({
|
|
106
|
+
id: name,
|
|
107
|
+
kind: S.Literal("repeat"),
|
|
108
|
+
body: name,
|
|
109
|
+
maxRounds: count(1, 5),
|
|
110
|
+
/** The predicate reads the completed body's output through $input. */
|
|
111
|
+
until: predicateSchema,
|
|
112
|
+
}),
|
|
113
|
+
S.Struct({ id: name, kind: S.Literal("wait"), for: duration }),
|
|
114
|
+
S.Struct({
|
|
115
|
+
id: name,
|
|
116
|
+
kind: S.Literal("transform"),
|
|
117
|
+
fields: record(mappingSchema),
|
|
118
|
+
}),
|
|
119
|
+
]);
|
|
120
|
+
export const edgeSchema = S.Struct({ from: portRefSchema, to: portRefSchema });
|
|
121
|
+
/**
|
|
122
|
+
* Ports of the event a trigger receives. Inside a trigger, `$input` names this
|
|
123
|
+
* event, not the run input: the trigger maps it onto the run's `$input`. Every
|
|
124
|
+
* port is the same on a redelivery of the same event, so a redelivery maps to
|
|
125
|
+
* the same run input and replays instead of conflicting.
|
|
126
|
+
*/
|
|
127
|
+
export const TRIGGER_EVENT_PORTS = {
|
|
128
|
+
webhook: ["body", "eventId", "headers"],
|
|
129
|
+
schedule: ["scheduledAt", "eventId"],
|
|
130
|
+
event: ["payload", "eventId"],
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* An incoming webhook. The host owns the URL, the secret and the signature
|
|
134
|
+
* scheme; the definition owns only the filter and the mapping onto run input.
|
|
135
|
+
* Without `input`, the event body itself must be the run input.
|
|
136
|
+
*/
|
|
137
|
+
const webhookTriggerSchema = S.Struct({
|
|
138
|
+
id: name,
|
|
139
|
+
on: S.Literal("webhook"),
|
|
140
|
+
title: S.optionalKey(boundedString(120)),
|
|
141
|
+
when: S.optionalKey(predicateSchema),
|
|
142
|
+
input: S.optionalKey(record(mappingSchema)),
|
|
143
|
+
});
|
|
144
|
+
const scheduleTriggerSchema = S.Struct({
|
|
145
|
+
id: name,
|
|
146
|
+
on: S.Literal("schedule"),
|
|
147
|
+
title: S.optionalKey(boundedString(120)),
|
|
148
|
+
cron: S.Struct({
|
|
149
|
+
expression: boundedString(256),
|
|
150
|
+
timeZone: boundedString(128),
|
|
151
|
+
}),
|
|
152
|
+
input: S.optionalKey(record(mappingSchema)),
|
|
153
|
+
});
|
|
154
|
+
const appEventTriggerSchema = S.Struct({
|
|
155
|
+
id: name,
|
|
156
|
+
on: S.Literal("event"),
|
|
157
|
+
title: S.optionalKey(boundedString(120)),
|
|
158
|
+
app: name,
|
|
159
|
+
event: boundedString(128),
|
|
160
|
+
eventDigest: boundedString(128),
|
|
161
|
+
when: S.optionalKey(predicateSchema),
|
|
162
|
+
input: S.optionalKey(record(mappingSchema)),
|
|
163
|
+
/**
|
|
164
|
+
* The one repository, in the event App's own Git namespace, this event is
|
|
165
|
+
* about. A node's `gitAccess` names it as `$trigger.repository`.
|
|
166
|
+
*/
|
|
167
|
+
repository: S.optionalKey(mappingSchema),
|
|
168
|
+
});
|
|
169
|
+
const triggerSchema = S.Union([
|
|
170
|
+
webhookTriggerSchema,
|
|
171
|
+
scheduleTriggerSchema,
|
|
172
|
+
appEventTriggerSchema,
|
|
173
|
+
]);
|
|
174
|
+
export const graphSchema = S.Struct({
|
|
175
|
+
input: projectionSchema,
|
|
176
|
+
nodes: array(nodeSchema, 64),
|
|
177
|
+
edges: array(edgeSchema, 256),
|
|
178
|
+
output: record(portRefSchema),
|
|
179
|
+
});
|
|
180
|
+
export const workflowDefinitionSchema = S.Struct({
|
|
181
|
+
...graphSchema.fields,
|
|
182
|
+
format: S.Literal("fungi.workflow/v1"),
|
|
183
|
+
title: boundedString(120),
|
|
184
|
+
description: S.optionalKey(boundedString(1000)),
|
|
185
|
+
apps: record(S.Struct({
|
|
186
|
+
appId: boundedString(256),
|
|
187
|
+
role: S.optionalKey(boundedString(80)),
|
|
188
|
+
})),
|
|
189
|
+
subgraphs: S.optionalKey(record(graphSchema)),
|
|
190
|
+
triggers: S.optionalKey(array(triggerSchema, 8)),
|
|
191
|
+
}).pipe(S.check(S.makeFilter((value) => new TextEncoder().encode(JSON.stringify(value)).length <= 256_000)));
|
|
192
|
+
/** Structural App API method shape from T1; digests are supplied by its catalog owner. */
|
|
193
|
+
export const methodSchema = S.Struct({
|
|
194
|
+
/** This method may yield Agar Pending before its completed output. */
|
|
195
|
+
pending: S.optionalKey(S.Literal(true)),
|
|
196
|
+
description: boundedString(1000),
|
|
197
|
+
input: projectionSchema,
|
|
198
|
+
output: projectionSchema,
|
|
199
|
+
effect: S.Literals(["read", "write", "external"]),
|
|
200
|
+
approval: S.Literals(["none", "queue", "simulate"]),
|
|
201
|
+
requires: array(boundedString(80), 64),
|
|
202
|
+
});
|
|
203
|
+
export const eventSchema = S.Struct({
|
|
204
|
+
description: boundedString(1000),
|
|
205
|
+
payload: projectionSchema,
|
|
206
|
+
});
|
|
207
|
+
export const catalogSchema = S.Record(S.String, S.Struct({
|
|
208
|
+
methods: S.Record(S.String, S.Struct({ ...methodSchema.fields, digest: boundedString(128) })),
|
|
209
|
+
events: S.optionalKey(S.Record(S.String, S.Struct({ ...eventSchema.fields, digest: boundedString(128) }))),
|
|
210
|
+
}));
|
|
211
|
+
export const bindingsSchema = record(S.declare((value) => workflowBindingSchema.safeParse(value).success));
|
|
212
|
+
export const planContentSchema = S.Struct({
|
|
213
|
+
definition: workflowDefinitionSchema,
|
|
214
|
+
catalog: catalogSchema,
|
|
215
|
+
bindings: bindingsSchema,
|
|
216
|
+
estimatedSteps: count(0, 800),
|
|
217
|
+
requiredBindings: array(S.Struct({
|
|
218
|
+
app: name,
|
|
219
|
+
installationId: boundedString(256),
|
|
220
|
+
method: boundedString(80),
|
|
221
|
+
...methodSchema.fields,
|
|
222
|
+
}), 64),
|
|
223
|
+
});
|
|
224
|
+
export const executionPlanSchema = S.Struct({
|
|
225
|
+
...planContentSchema.fields,
|
|
226
|
+
planDigest: boundedString(128),
|
|
227
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import type { Json, WorkflowDefinition } from "./schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* One verified incoming event, as the host hands it to a trigger. The host has
|
|
5
|
+
* already authenticated the sender and bounded the body. `eventId` is the
|
|
6
|
+
* host's stable, non-secret identity for the event; `headers` holds only the
|
|
7
|
+
* host's allowlisted, non-secret headers that a redelivery repeats.
|
|
8
|
+
*/
|
|
9
|
+
export type TriggerEvent = Readonly<{
|
|
10
|
+
body: Json;
|
|
11
|
+
eventId: string;
|
|
12
|
+
headers: Readonly<Record<string, string>>;
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
scheduledAt: string;
|
|
15
|
+
eventId: string;
|
|
16
|
+
}> | Readonly<{
|
|
17
|
+
payload: Json;
|
|
18
|
+
eventId: string;
|
|
19
|
+
}>;
|
|
20
|
+
export type TriggerOutcome = Readonly<{
|
|
21
|
+
kind: "start";
|
|
22
|
+
input: S.JsonObject;
|
|
23
|
+
repository?: string;
|
|
24
|
+
}> | Readonly<{
|
|
25
|
+
kind: "ignored";
|
|
26
|
+
}> | Readonly<{
|
|
27
|
+
kind: "refused";
|
|
28
|
+
code: "unknown_trigger" | "invalid_input" | "invalid_repository";
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Decide what one event does to a Workflow: start a run with typed input,
|
|
32
|
+
* nothing (its filter does not select the event), or a refusal. Pure; the
|
|
33
|
+
* definition has already passed `validate`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function triggerRun(definition: WorkflowDefinition, triggerId: string, event: TriggerEvent): TriggerOutcome;
|
|
36
|
+
/**
|
|
37
|
+
* The idempotent start key for a triggered run. `scope` is the host's name for
|
|
38
|
+
* where the event arrived (for example a webhook binding); a redelivery of the
|
|
39
|
+
* same event through the same trigger yields the same key, so it starts at
|
|
40
|
+
* most one run.
|
|
41
|
+
*/
|
|
42
|
+
export declare function triggerRequestId(input: Readonly<{
|
|
43
|
+
scope: string;
|
|
44
|
+
triggerId: string;
|
|
45
|
+
eventId: string;
|
|
46
|
+
}>): Promise<string>;
|
package/dist/triggers.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import { contentDigest } from "./compile.js";
|
|
3
|
+
import { evaluateMapping, evaluatePredicate } from "./expressions.js";
|
|
4
|
+
import { admitRunInput } from "./ports.js";
|
|
5
|
+
import { triggerRepositorySchema } from "./git-access.js";
|
|
6
|
+
/**
|
|
7
|
+
* Decide what one event does to a Workflow: start a run with typed input,
|
|
8
|
+
* nothing (its filter does not select the event), or a refusal. Pure; the
|
|
9
|
+
* definition has already passed `validate`.
|
|
10
|
+
*/
|
|
11
|
+
export function triggerRun(definition, triggerId, event) {
|
|
12
|
+
const trigger = definition.triggers?.find((item) => item.id === triggerId);
|
|
13
|
+
if (trigger === undefined)
|
|
14
|
+
return { kind: "refused", code: "unknown_trigger" };
|
|
15
|
+
let eventInput;
|
|
16
|
+
let defaultInput;
|
|
17
|
+
switch (trigger.on) {
|
|
18
|
+
case "webhook":
|
|
19
|
+
if (!("body" in event))
|
|
20
|
+
return { kind: "refused", code: "invalid_input" };
|
|
21
|
+
eventInput = {
|
|
22
|
+
body: event.body,
|
|
23
|
+
eventId: event.eventId,
|
|
24
|
+
headers: { ...event.headers },
|
|
25
|
+
};
|
|
26
|
+
defaultInput = event.body;
|
|
27
|
+
break;
|
|
28
|
+
case "schedule":
|
|
29
|
+
if (!("scheduledAt" in event))
|
|
30
|
+
return { kind: "refused", code: "invalid_input" };
|
|
31
|
+
eventInput = { scheduledAt: event.scheduledAt, eventId: event.eventId };
|
|
32
|
+
defaultInput = eventInput;
|
|
33
|
+
break;
|
|
34
|
+
case "event":
|
|
35
|
+
if (!("payload" in event))
|
|
36
|
+
return { kind: "refused", code: "invalid_input" };
|
|
37
|
+
eventInput = { payload: event.payload, eventId: event.eventId };
|
|
38
|
+
defaultInput = event.payload;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
const values = { $input: eventInput };
|
|
42
|
+
if (trigger.on !== "schedule" &&
|
|
43
|
+
trigger.when !== undefined &&
|
|
44
|
+
!evaluatePredicate(trigger.when, values))
|
|
45
|
+
return { kind: "ignored" };
|
|
46
|
+
const input = trigger.input === undefined
|
|
47
|
+
? defaultInput
|
|
48
|
+
: Object.fromEntries(Object.entries(trigger.input).flatMap(([key, mapping]) => {
|
|
49
|
+
const value = evaluateMapping(mapping, values);
|
|
50
|
+
return value === undefined ? [] : [[key, value]];
|
|
51
|
+
}));
|
|
52
|
+
const admitted = admitRunInput(definition, input);
|
|
53
|
+
if (admitted.kind === "invalid")
|
|
54
|
+
return { kind: "refused", code: "invalid_input" };
|
|
55
|
+
if (trigger.on !== "event" || trigger.repository === undefined)
|
|
56
|
+
return { kind: "start", input: admitted.input };
|
|
57
|
+
const repository = triggerRepositorySchema.safeParse(evaluateMapping(trigger.repository, values));
|
|
58
|
+
return repository.success
|
|
59
|
+
? { kind: "start", input: admitted.input, repository: repository.data }
|
|
60
|
+
: { kind: "refused", code: "invalid_repository" };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The idempotent start key for a triggered run. `scope` is the host's name for
|
|
64
|
+
* where the event arrived (for example a webhook binding); a redelivery of the
|
|
65
|
+
* same event through the same trigger yields the same key, so it starts at
|
|
66
|
+
* most one run.
|
|
67
|
+
*/
|
|
68
|
+
export function triggerRequestId(input) {
|
|
69
|
+
return contentDigest([
|
|
70
|
+
"fungi.workflow/v1 trigger",
|
|
71
|
+
input.scope,
|
|
72
|
+
input.triggerId,
|
|
73
|
+
input.eventId,
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Catalog, type Graph, type WorkflowDefinition, type Node, type PortRef } from "./schema.js";
|
|
2
|
+
export type IssueCode = "schema" | "unknown_app" | "unknown_method" | "unknown_event" | "digest_drift" | "unknown_port" | "unassignable_edge" | "required_port" | "cycle" | "unreachable" | "step_budget" | "template" | "duplicate" | "subgraph" | "binding" | "literal" | "trigger";
|
|
3
|
+
export type Issue = {
|
|
4
|
+
readonly code: IssueCode;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly graph?: string;
|
|
7
|
+
readonly node?: string;
|
|
8
|
+
readonly trigger?: string;
|
|
9
|
+
readonly port?: string;
|
|
10
|
+
readonly edge?: number;
|
|
11
|
+
};
|
|
12
|
+
export type Analysis = {
|
|
13
|
+
readonly issues: readonly Issue[];
|
|
14
|
+
readonly definition?: WorkflowDefinition;
|
|
15
|
+
readonly catalog?: Catalog;
|
|
16
|
+
readonly estimatedSteps: number;
|
|
17
|
+
readonly order: Readonly<Record<string, readonly Node[]>>;
|
|
18
|
+
};
|
|
19
|
+
/** CF permits 1024 durable steps; this private ceiling leaves execution headroom. */
|
|
20
|
+
export declare const STEP_BUDGET = 800;
|
|
21
|
+
export declare function dependencies(graph: Graph, node: Node): readonly PortRef[];
|
|
22
|
+
export declare function topological(graph: Graph): readonly Node[];
|
|
23
|
+
/** Decode JSON boundaries once, then collect domain issues for the editor. */
|
|
24
|
+
export declare function analyze(raw: WorkflowDefinition, rawCatalog: Catalog): Analysis;
|
|
25
|
+
export declare function validate(definition: unknown, catalog: unknown): readonly Issue[];
|