@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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -2
  3. package/dist/cloudflare/graph-engine.d.ts +25 -0
  4. package/dist/cloudflare/graph-engine.js +46 -0
  5. package/dist/cloudflare/graph-program.d.ts +6 -0
  6. package/dist/cloudflare/graph-program.js +528 -0
  7. package/dist/cloudflare/index.d.ts +1 -0
  8. package/dist/cloudflare/index.js +1 -0
  9. package/dist/code-execution.d.ts +16 -0
  10. package/dist/code-execution.js +50 -0
  11. package/dist/code-result.d.ts +11 -0
  12. package/dist/code-result.js +19 -0
  13. package/dist/code-sandbox.d.ts +9 -0
  14. package/dist/code-sandbox.js +13 -0
  15. package/dist/compile.d.ts +41 -0
  16. package/dist/compile.js +240 -0
  17. package/dist/execution.d.ts +113 -0
  18. package/dist/execution.js +108 -0
  19. package/dist/expressions.d.ts +16 -0
  20. package/dist/expressions.js +322 -0
  21. package/dist/git-access.d.ts +59 -0
  22. package/dist/git-access.js +106 -0
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.js +8 -0
  25. package/dist/ports.d.ts +58 -0
  26. package/dist/ports.js +256 -0
  27. package/dist/runtime/arming-authority.d.ts +83 -0
  28. package/dist/runtime/arming-authority.js +90 -0
  29. package/dist/runtime/bindings.d.ts +25 -0
  30. package/dist/runtime/bindings.js +41 -0
  31. package/dist/runtime/capture.d.ts +18 -0
  32. package/dist/runtime/capture.js +99 -0
  33. package/dist/runtime/git-pass-contract.d.ts +63 -0
  34. package/dist/runtime/git-pass-contract.js +36 -0
  35. package/dist/runtime/git-pass-store.d.ts +32 -0
  36. package/dist/runtime/git-pass-store.js +82 -0
  37. package/dist/runtime/git-pass.d.ts +41 -0
  38. package/dist/runtime/git-pass.js +72 -0
  39. package/dist/runtime/graph-contract.d.ts +784 -0
  40. package/dist/runtime/graph-contract.js +122 -0
  41. package/dist/runtime/graph-dispatch.d.ts +31 -0
  42. package/dist/runtime/graph-dispatch.js +111 -0
  43. package/dist/runtime/graph-run-store.d.ts +73 -0
  44. package/dist/runtime/graph-run-store.js +267 -0
  45. package/dist/runtime/graph-start.d.ts +90 -0
  46. package/dist/runtime/graph-start.js +124 -0
  47. package/dist/runtime/index.d.ts +20 -0
  48. package/dist/runtime/index.js +20 -0
  49. package/dist/runtime/ports.d.ts +153 -0
  50. package/dist/runtime/ports.js +1 -0
  51. package/dist/runtime/run-authority.d.ts +60 -0
  52. package/dist/runtime/run-authority.js +145 -0
  53. package/dist/runtime/run-ceiling.d.ts +106 -0
  54. package/dist/runtime/run-ceiling.js +23 -0
  55. package/dist/runtime/run-member.d.ts +3 -0
  56. package/dist/runtime/run-member.js +12 -0
  57. package/dist/runtime/run-owner.d.ts +1628 -0
  58. package/dist/runtime/run-owner.js +513 -0
  59. package/dist/runtime/run-report.d.ts +14 -0
  60. package/dist/runtime/run-report.js +23 -0
  61. package/dist/runtime/schedules.d.ts +39 -0
  62. package/dist/runtime/schedules.js +149 -0
  63. package/dist/runtime/stored-trigger-rows.d.ts +12 -0
  64. package/dist/runtime/stored-trigger-rows.js +17 -0
  65. package/dist/runtime/trigger-owner.d.ts +590 -0
  66. package/dist/runtime/trigger-owner.js +280 -0
  67. package/dist/runtime/trigger-plans.d.ts +766 -0
  68. package/dist/runtime/trigger-plans.js +62 -0
  69. package/dist/runtime/trigger-router.d.ts +34 -0
  70. package/dist/runtime/trigger-router.js +143 -0
  71. package/dist/runtime/trigger-run-reservations.d.ts +28 -0
  72. package/dist/runtime/trigger-run-reservations.js +114 -0
  73. package/dist/schema.d.ts +1111 -0
  74. package/dist/schema.js +227 -0
  75. package/dist/triggers.d.ts +46 -0
  76. package/dist/triggers.js +75 -0
  77. package/dist/validate.d.ts +25 -0
  78. package/dist/validate.js +679 -0
  79. package/dist/workflow-id.d.ts +3 -0
  80. package/dist/workflow-id.js +3 -0
  81. package/package.json +95 -3
@@ -0,0 +1,41 @@
1
+ import { type Issue } from "./validate.js";
2
+ import { type ExecutionPlan, type Json, type Catalog, type Bindings, type WorkflowDefinition } from "./schema.js";
3
+ /** Canonical object ordering; array order remains semantic. No execution layout is accepted. */
4
+ export declare function canonicalJson(value: Json): string;
5
+ export declare function contentDigest(value: Json): Promise<string>;
6
+ export type CompileResult = {
7
+ readonly ok: false;
8
+ readonly issues: readonly Issue[];
9
+ } | {
10
+ readonly ok: true;
11
+ readonly plan: ExecutionPlan;
12
+ };
13
+ export declare function compile(raw: WorkflowDefinition, rawCatalog: Catalog, rawBindings: Bindings): Promise<CompileResult>;
14
+ /** Stored plans are parsed and their content identity verified before interpretation. */
15
+ export declare function decodeExecutionPlan(raw: unknown): Promise<ExecutionPlan>;
16
+ /** One App method as an App's `api` block declares it; structural, no App SDK import. */
17
+ export type DeclaredMethod = Readonly<{
18
+ /** Opt in to the shared Pending control result. */
19
+ pending?: true | undefined;
20
+ /** Discovery-only metadata from the App declaration; hidden methods have no graph ports. */
21
+ hidden?: true | undefined;
22
+ description: string;
23
+ input: object;
24
+ output: object;
25
+ effect: "read" | "write" | "external";
26
+ requires: readonly string[];
27
+ }>;
28
+ /**
29
+ * The catalog entry for one App's declared methods. The digest covers what a
30
+ * node depends on (ports, effect, approval, requires), not the description, so
31
+ * rewording a method does not drift Workflows. External effects queue for
32
+ * approval; v1 declares no other approval policy.
33
+ */
34
+ export declare function catalogMethods(methods: Readonly<Record<string, DeclaredMethod>>): Promise<Catalog[string]>;
35
+ /** Event identity pins the whole emitted schema; descriptions do not affect compatibility. */
36
+ export declare function catalogEvents(events: Readonly<Record<string, {
37
+ description: string;
38
+ payload: object;
39
+ }>>): Promise<{
40
+ events: NonNullable<Catalog[string]["events"]>;
41
+ }>;
@@ -0,0 +1,240 @@
1
+ import * as S from "effect/Schema";
2
+ import * as Option from "effect/Option";
3
+ import { analyze } from "./validate.js";
4
+ import { bindingsSchema, executionPlanSchema, methodSchema, eventSchema, projectionSchema, } from "./schema.js";
5
+ /** Canonical object ordering; array order remains semantic. No execution layout is accepted. */
6
+ export function canonicalJson(value) {
7
+ if (Array.isArray(value))
8
+ return "[" + value.map(canonicalJson).join(",") + "]";
9
+ if (S.is(S.JsonObject)(value))
10
+ return ("{" +
11
+ Object.keys(value)
12
+ .sort()
13
+ .map((key) => JSON.stringify(key) + ":" + canonicalJson(value[key] ?? null))
14
+ .join(",") +
15
+ "}");
16
+ return JSON.stringify(value);
17
+ }
18
+ export async function contentDigest(value) {
19
+ const json = S.decodeUnknownSync(S.Json)(value);
20
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(json)));
21
+ return ("sha256:" +
22
+ [...new Uint8Array(hash)]
23
+ .map((byte) => byte.toString(16).padStart(2, "0"))
24
+ .join(""));
25
+ }
26
+ export async function compile(raw, rawCatalog, rawBindings) {
27
+ const analysis = analyze(raw, rawCatalog);
28
+ if (analysis.issues.length || !analysis.definition || !analysis.catalog)
29
+ return { ok: false, issues: analysis.issues };
30
+ let bindings;
31
+ try {
32
+ bindings = S.decodeUnknownSync(bindingsSchema)(rawBindings, {
33
+ onExcessProperty: "error",
34
+ });
35
+ }
36
+ catch (cause) {
37
+ if (!S.isSchemaError(cause))
38
+ throw cause;
39
+ return {
40
+ ok: false,
41
+ issues: [{ code: "binding", message: "Invalid installation bindings" }],
42
+ };
43
+ }
44
+ const { definition, catalog } = analysis;
45
+ const requiredBindings = [];
46
+ const pinned = {};
47
+ const bound = {};
48
+ const issues = [];
49
+ const seen = new Set();
50
+ for (const node of Object.values(analysis.order).flat()) {
51
+ if (node.kind !== "app")
52
+ continue;
53
+ const app = definition.apps[node.app];
54
+ const binding = bindings[node.app];
55
+ if (!app ||
56
+ binding === undefined ||
57
+ !("installationId" in binding) ||
58
+ !binding.installationId ||
59
+ binding.appId !== app.appId) {
60
+ issues.push({
61
+ code: "binding",
62
+ node: node.id,
63
+ message: "App binding missing or targets a different App",
64
+ });
65
+ continue;
66
+ }
67
+ const method = catalog[app.appId]?.methods[node.method];
68
+ if (!method)
69
+ continue;
70
+ pinned[app.appId] = {
71
+ methods: { ...pinned[app.appId]?.methods, [node.method]: method },
72
+ };
73
+ bound[node.app] = binding;
74
+ if (node.gitAccess !== undefined) {
75
+ const target = bindings[node.gitAccess.binding];
76
+ if (target === undefined) {
77
+ issues.push({
78
+ code: "binding",
79
+ node: node.id,
80
+ message: "Git repository binding is missing",
81
+ });
82
+ continue;
83
+ }
84
+ bound[node.gitAccess.binding] = target;
85
+ }
86
+ const key = node.app + "." + node.method;
87
+ if (seen.has(key))
88
+ continue;
89
+ seen.add(key);
90
+ const { description, input, output, effect, approval, requires } = method;
91
+ requiredBindings.push({
92
+ app: node.app,
93
+ installationId: binding.installationId,
94
+ method: node.method,
95
+ description,
96
+ input,
97
+ output,
98
+ effect,
99
+ approval,
100
+ requires,
101
+ });
102
+ }
103
+ for (const trigger of definition.triggers ?? []) {
104
+ if (trigger.on !== "event")
105
+ continue;
106
+ const app = definition.apps[trigger.app];
107
+ const binding = bindings[trigger.app];
108
+ if (!app ||
109
+ binding === undefined ||
110
+ !("installationId" in binding) ||
111
+ !binding.installationId ||
112
+ binding.appId !== app.appId) {
113
+ issues.push({
114
+ code: "binding",
115
+ trigger: trigger.id,
116
+ message: "Event App binding missing or targets a different App",
117
+ });
118
+ continue;
119
+ }
120
+ const event = catalog[app.appId]?.events?.[trigger.event];
121
+ if (!event)
122
+ continue;
123
+ const prior = pinned[app.appId];
124
+ pinned[app.appId] = {
125
+ methods: prior?.methods ?? {},
126
+ events: { ...prior?.events, [trigger.event]: event },
127
+ };
128
+ bound[trigger.app] = binding;
129
+ }
130
+ if (issues.length)
131
+ return { ok: false, issues };
132
+ let sortedDefinition = {
133
+ ...definition,
134
+ nodes: analysis.order.root ?? [],
135
+ edges: [...definition.edges].sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))),
136
+ };
137
+ if (definition.subgraphs) {
138
+ sortedDefinition = {
139
+ ...sortedDefinition,
140
+ subgraphs: Object.fromEntries(Object.entries(definition.subgraphs).map(([id, graph]) => [
141
+ id,
142
+ {
143
+ ...graph,
144
+ nodes: analysis.order[id] ?? [],
145
+ edges: [...graph.edges].sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))),
146
+ },
147
+ ])),
148
+ };
149
+ }
150
+ const content = {
151
+ definition: sortedDefinition,
152
+ catalog: pinned,
153
+ bindings: bound,
154
+ estimatedSteps: analysis.estimatedSteps,
155
+ requiredBindings: requiredBindings.sort((a, b) => (a.app + a.method).localeCompare(b.app + b.method)),
156
+ };
157
+ return {
158
+ ok: true,
159
+ plan: { ...content, planDigest: await contentDigest(content) },
160
+ };
161
+ }
162
+ /** Stored plans are parsed and their content identity verified before interpretation. */
163
+ export async function decodeExecutionPlan(raw) {
164
+ const plan = S.decodeUnknownSync(executionPlanSchema)(raw, {
165
+ onExcessProperty: "error",
166
+ });
167
+ const { planDigest, ...content } = plan;
168
+ if ((await contentDigest(content)) !== planDigest)
169
+ throw new Error("Workflow plan digest mismatch");
170
+ const rebuilt = await compile(plan.definition, plan.catalog, plan.bindings);
171
+ if (!rebuilt.ok || rebuilt.plan.planDigest !== planDigest)
172
+ throw new Error("Workflow plan is not a compiled v1 plan");
173
+ return plan;
174
+ }
175
+ // The emitted draft identifier is metadata; graph ports use the completion schema.
176
+ function schemaView(schema) {
177
+ return Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$schema"));
178
+ }
179
+ // Agar emits exactly [declared Output, Pending]. This projects its completion
180
+ // side only; unsupported nested unions remain unsupported graph port schemas.
181
+ const emittedPendingUnion = S.Struct({
182
+ anyOf: S.Tuple([projectionSchema, projectionSchema]),
183
+ });
184
+ function completionView(method) {
185
+ const output = schemaView(method.output);
186
+ if (method.pending !== true)
187
+ return output;
188
+ const union = S.decodeUnknownOption(emittedPendingUnion, {
189
+ onExcessProperty: "error",
190
+ })(output);
191
+ return Option.isSome(union) ? schemaView(union.value.anyOf[0]) : output;
192
+ }
193
+ /**
194
+ * The catalog entry for one App's declared methods. The digest covers what a
195
+ * node depends on (ports, effect, approval, requires), not the description, so
196
+ * rewording a method does not drift Workflows. External effects queue for
197
+ * approval; v1 declares no other approval policy.
198
+ */
199
+ export async function catalogMethods(methods) {
200
+ const entries = await Promise.all(Object.entries(methods)
201
+ .flatMap(([name, { hidden, ...declared }]) => hidden === true ? [] : [[name, declared]])
202
+ .map(async ([name, declared]) => {
203
+ const method = S.decodeUnknownSync(methodSchema)({
204
+ ...declared,
205
+ requires: [...declared.requires],
206
+ approval: declared.effect === "external" ? "queue" : "none",
207
+ }, { onExcessProperty: "error" });
208
+ const pinned = {
209
+ input: method.input,
210
+ output: method.output,
211
+ effect: method.effect,
212
+ approval: method.approval,
213
+ requires: method.requires,
214
+ };
215
+ const digestInput = method.pending === true ? { ...pinned, pending: true } : pinned;
216
+ return [
217
+ name,
218
+ {
219
+ ...method,
220
+ input: schemaView(method.input),
221
+ output: completionView(method),
222
+ digest: await contentDigest(digestInput),
223
+ },
224
+ ];
225
+ }));
226
+ return { methods: Object.fromEntries(entries) };
227
+ }
228
+ /** Event identity pins the whole emitted schema; descriptions do not affect compatibility. */
229
+ export async function catalogEvents(events) {
230
+ const entries = await Promise.all(Object.entries(events).map(async ([name, raw]) => {
231
+ const event = S.decodeUnknownSync(eventSchema)(raw, {
232
+ onExcessProperty: "error",
233
+ });
234
+ return [
235
+ name,
236
+ { ...event, digest: await contentDigest({ payload: event.payload }) },
237
+ ];
238
+ }));
239
+ return { events: Object.fromEntries(entries) };
240
+ }
@@ -0,0 +1,113 @@
1
+ import * as S from "effect/Schema";
2
+ import type { ExecutionPlan, Json, Method } from "./schema.js";
3
+ export declare const graphParamsSchema: S.Union<readonly [S.Struct<{
4
+ readonly kind: S.Literal<"run">;
5
+ readonly teamId: S.String;
6
+ readonly runId: S.String;
7
+ readonly planDigest: S.String;
8
+ }>, S.Struct<{
9
+ readonly kind: S.Literal<"map-item">;
10
+ readonly teamId: S.String;
11
+ readonly runId: S.String;
12
+ readonly planDigest: S.String;
13
+ readonly body: S.String;
14
+ readonly iteration: S.String;
15
+ readonly parentInstanceId: S.String;
16
+ readonly completionEvent: S.String;
17
+ readonly input: S.$Record<S.String, S.Codec<S.Json, S.Json, never, never>>;
18
+ }>]>;
19
+ export type GraphParams = typeof graphParamsSchema.Type;
20
+ export declare const nodeEventSchema: S.Struct<{
21
+ readonly runId: S.String;
22
+ readonly graphId: S.optionalKey<S.String>;
23
+ readonly nodeId: S.String;
24
+ readonly iteration: S.String;
25
+ readonly poll: S.optionalKey<S.Number>;
26
+ readonly state: S.Literals<readonly ["queued", "running", "waiting", "complete", "failed", "skipped"]>;
27
+ readonly attempt: S.Number;
28
+ /** Time the host durably recorded this lifecycle fact. */
29
+ readonly recordedAt: S.optionalKey<S.Number>;
30
+ readonly durationMs: S.optionalKey<S.Number>;
31
+ readonly costMilliunits: S.optionalKey<S.Number>;
32
+ readonly outputDigest: S.optionalKey<S.String>;
33
+ readonly output: S.optionalKey<S.$Record<S.String, S.Codec<S.Json, S.Json, never, never>>>;
34
+ readonly errorMessage: S.optionalKey<S.String>;
35
+ readonly refusal: S.optionalKey<S.String>;
36
+ }>;
37
+ export type NodeEvent = typeof nodeEventSchema.Type;
38
+ export declare const graphReviewSchema: S.Struct<{
39
+ readonly runId: S.String;
40
+ readonly nodeId: S.String;
41
+ readonly iteration: S.String;
42
+ readonly instanceId: S.String;
43
+ readonly eventType: S.String;
44
+ readonly title: S.String;
45
+ readonly reviewers: S.Literals<readonly ["runner", "owners"]>;
46
+ readonly timeout: S.Number;
47
+ readonly show: S.$Array<S.Codec<S.Json, S.Json, never, never>>;
48
+ /** The review's named choices; absent for an approve/reject review. */
49
+ readonly choices: S.optionalKey<S.$Array<S.String>>;
50
+ }>;
51
+ export type GraphReview = typeof graphReviewSchema.Type;
52
+ /** Approve or reject a plain review, or pick one of a review's named choices. */
53
+ export declare const reviewAnswerSchema: S.Union<readonly [S.Struct<{
54
+ readonly approved: S.Boolean;
55
+ }>, S.Struct<{
56
+ readonly choice: S.String;
57
+ readonly note: S.optionalKey<S.String>;
58
+ }>]>;
59
+ /** Project one decoded review answer into the review node's output ports. */
60
+ export declare function reviewAnswerOutput(answer: typeof reviewAnswerSchema.Type, item: Json): {
61
+ [x: string]: S.Json;
62
+ choice: string;
63
+ note: string;
64
+ };
65
+ export declare const dispatchResultSchema: S.Union<readonly [S.Struct<{
66
+ readonly kind: S.Literal<"complete">;
67
+ readonly output: S.Codec<S.Json, S.Json, never, never>;
68
+ readonly costMilliunits: S.Number;
69
+ }>, S.Struct<{
70
+ readonly kind: S.Literal<"refused">;
71
+ readonly code: S.Literals<readonly ["not_granted", "invalid_input", "invalid_output", "revoked", "member_revoked", "workflow_run_revoked", "computer_revoked", "budget_exhausted", "unknown_method"]>;
72
+ }>, S.Struct<{
73
+ readonly kind: S.Literal<"pending_approval">;
74
+ readonly actionId: S.String;
75
+ }>, S.Struct<{
76
+ readonly kind: S.Literal<"pending">;
77
+ readonly retryAfterMs: S.Number;
78
+ }>]>;
79
+ export type DispatchResult = typeof dispatchResultSchema.Type;
80
+ /** Live admission before any node kind; refusals use the engine's sole vocabulary. */
81
+ export declare const nodeAdmissionResultSchema: S.Union<readonly [S.Struct<{
82
+ readonly kind: S.Literal<"admitted">;
83
+ }>, S.Struct<{
84
+ readonly kind: S.Literal<"refused">;
85
+ readonly code: S.Literals<readonly ["not_granted", "invalid_input", "invalid_output", "revoked", "member_revoked", "workflow_run_revoked", "computer_revoked", "budget_exhausted", "unknown_method"]>;
86
+ }>]>;
87
+ export type NodeAdmissionResult = typeof nodeAdmissionResultSchema.Type;
88
+ export type NodeCall = {
89
+ readonly teamId: string;
90
+ readonly runId: string;
91
+ readonly graphId?: string;
92
+ readonly nodeId: string;
93
+ readonly iteration: string;
94
+ readonly poll?: number;
95
+ readonly installationId: string;
96
+ readonly method: string;
97
+ readonly methodSpec: Method;
98
+ readonly input: Json;
99
+ readonly idempotencyKey: string;
100
+ };
101
+ /** Host ports: load an admitted plan, invoke Apps, and persist execution facts. */
102
+ export type NodeDispatcher = {
103
+ loadRun(teamId: string, runId: string): Promise<{
104
+ readonly plan: ExecutionPlan;
105
+ readonly input: Readonly<Record<string, Json>>;
106
+ }>;
107
+ /** Recheck the run's member and live library grant before the node executes. */
108
+ beforeNode(event: NodeEvent): Promise<NodeAdmissionResult>;
109
+ dispatch(call: NodeCall): Promise<DispatchResult>;
110
+ recordEvent(event: NodeEvent): Promise<void>;
111
+ /** Persist once and return the owner-held expiration, unchanged on replay. */
112
+ openReview(review: GraphReview): Promise<number>;
113
+ };
@@ -0,0 +1,108 @@
1
+ import * as S from "effect/Schema";
2
+ const id = S.String.pipe(S.check(S.isMinLength(1)), S.check(S.isMaxLength(256)));
3
+ const natural = S.Number.pipe(S.check(S.isInt()), S.check(S.isGreaterThanOrEqualTo(0)));
4
+ export const graphParamsSchema = S.Union([
5
+ S.Struct({ kind: S.Literal("run"), teamId: id, runId: id, planDigest: id }),
6
+ S.Struct({
7
+ kind: S.Literal("map-item"),
8
+ teamId: id,
9
+ runId: id,
10
+ planDigest: id,
11
+ body: id,
12
+ iteration: id,
13
+ parentInstanceId: id,
14
+ completionEvent: id,
15
+ input: S.JsonObject,
16
+ }),
17
+ ]);
18
+ export const nodeEventSchema = S.Struct({
19
+ runId: id,
20
+ graphId: S.optionalKey(id),
21
+ nodeId: id,
22
+ iteration: id,
23
+ poll: S.optionalKey(natural),
24
+ state: S.Literals([
25
+ "queued",
26
+ "running",
27
+ "waiting",
28
+ "complete",
29
+ "failed",
30
+ "skipped",
31
+ ]),
32
+ attempt: natural,
33
+ /** Time the host durably recorded this lifecycle fact. */
34
+ recordedAt: S.optionalKey(natural),
35
+ durationMs: S.optionalKey(natural),
36
+ costMilliunits: S.optionalKey(natural),
37
+ outputDigest: S.optionalKey(id),
38
+ output: S.optionalKey(S.JsonObject),
39
+ errorMessage: S.optionalKey(S.String.check(S.isMaxLength(1024))),
40
+ refusal: S.optionalKey(id),
41
+ });
42
+ export const graphReviewSchema = S.Struct({
43
+ runId: id,
44
+ nodeId: id,
45
+ iteration: id,
46
+ instanceId: id,
47
+ eventType: id,
48
+ title: S.String,
49
+ reviewers: S.Literals(["runner", "owners"]),
50
+ timeout: natural,
51
+ show: S.Array(S.Json),
52
+ /** The review's named choices; absent for an approve/reject review. */
53
+ choices: S.optionalKey(S.Array(id)),
54
+ });
55
+ /** Approve or reject a plain review, or pick one of a review's named choices. */
56
+ export const reviewAnswerSchema = S.Union([
57
+ S.Struct({ approved: S.Boolean }),
58
+ S.Struct({
59
+ choice: id,
60
+ note: S.optionalKey(S.String.check(S.isMaxLength(1000))),
61
+ }),
62
+ ]);
63
+ /** Project one decoded review answer into the review node's output ports. */
64
+ export function reviewAnswerOutput(answer, item) {
65
+ const choice = "choice" in answer
66
+ ? answer.choice
67
+ : answer.approved
68
+ ? "approved"
69
+ : "rejected";
70
+ return {
71
+ [choice]: item,
72
+ choice,
73
+ note: "choice" in answer ? (answer.note ?? "") : "",
74
+ };
75
+ }
76
+ const refusalCodes = [
77
+ "not_granted",
78
+ "invalid_input",
79
+ "invalid_output",
80
+ "revoked",
81
+ "member_revoked",
82
+ "workflow_run_revoked",
83
+ "computer_revoked",
84
+ "budget_exhausted",
85
+ "unknown_method",
86
+ ];
87
+ export const dispatchResultSchema = S.Union([
88
+ S.Struct({
89
+ kind: S.Literal("complete"),
90
+ output: S.Json,
91
+ costMilliunits: natural,
92
+ }),
93
+ S.Struct({ kind: S.Literal("refused"), code: S.Literals(refusalCodes) }),
94
+ S.Struct({ kind: S.Literal("pending_approval"), actionId: id }),
95
+ /**
96
+ * The App accepted the work and it is still running. The engine calls the
97
+ * same method again with the same idempotency key after `retryAfterMs`.
98
+ */
99
+ S.Struct({
100
+ kind: S.Literal("pending"),
101
+ retryAfterMs: S.Number.pipe(S.check(S.isInt()), S.check(S.isBetween({ minimum: 1_000, maximum: 60_000 }))),
102
+ }),
103
+ ]);
104
+ /** Live admission before any node kind; refusals use the engine's sole vocabulary. */
105
+ export const nodeAdmissionResultSchema = S.Union([
106
+ S.Struct({ kind: S.Literal("admitted") }),
107
+ S.Struct({ kind: S.Literal("refused"), code: S.Literals(refusalCodes) }),
108
+ ]);
@@ -0,0 +1,16 @@
1
+ import type { Mapping, Node, Predicate, PortRef, Json } from "./schema.js";
2
+ import { type PortSchema } from "./ports.js";
3
+ export type Values = Readonly<Record<string, Readonly<Record<string, Json>>>>;
4
+ /** Expected syntax refusal only; defects in a parser or graph traversal escape. */
5
+ export declare class ExpressionSyntaxError extends Error {
6
+ }
7
+ /** Mustache parses the representation; only its interpolation tokens belong to v1. */
8
+ export declare function templateRefs(template: string): readonly PortRef[];
9
+ export declare function mappingRefs(mapping: Mapping): readonly PortRef[];
10
+ export declare function predicateRefs(test: Predicate): readonly PortRef[];
11
+ export declare function nodeRefs(node: Node): readonly PortRef[];
12
+ export declare function readPort(values: Values, ref: PortRef): Json | undefined;
13
+ /** Infer constructed data through the same Mapping grammar used at execution. */
14
+ export declare function mappingProjection(mapping: Mapping, source: (ref: PortRef, path?: string) => PortSchema | undefined): PortSchema;
15
+ export declare function evaluateMapping(mapping: Mapping, values: Values): Json | undefined;
16
+ export declare function evaluatePredicate(test: Predicate, values: Values): boolean;