@driftgate/workflow-compiler 0.1.0-rc.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.
@@ -0,0 +1,199 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const WorkflowYamlSchema: z.ZodObject<{
4
+ apiVersion: z.ZodLiteral<"driftgate.ai/v1">;
5
+ kind: z.ZodLiteral<"Workflow">;
6
+ metadata: z.ZodObject<{
7
+ name: z.ZodString;
8
+ workspace: z.ZodString;
9
+ }, "strip", z.ZodTypeAny, {
10
+ name: string;
11
+ workspace: string;
12
+ }, {
13
+ name: string;
14
+ workspace: string;
15
+ }>;
16
+ spec: z.ZodObject<{
17
+ governance: z.ZodDefault<z.ZodObject<{
18
+ policyBindings: z.ZodDefault<z.ZodArray<z.ZodObject<{
19
+ policyId: z.ZodString;
20
+ mode: z.ZodDefault<z.ZodEnum<["monitor", "enforce"]>>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ policyId: string;
23
+ mode: "monitor" | "enforce";
24
+ }, {
25
+ policyId: string;
26
+ mode?: "monitor" | "enforce" | undefined;
27
+ }>, "many">>;
28
+ slaBindings: z.ZodDefault<z.ZodArray<z.ZodObject<{
29
+ slaId: z.ZodString;
30
+ }, "strip", z.ZodTypeAny, {
31
+ slaId: string;
32
+ }, {
33
+ slaId: string;
34
+ }>, "many">>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ policyBindings: {
37
+ policyId: string;
38
+ mode: "monitor" | "enforce";
39
+ }[];
40
+ slaBindings: {
41
+ slaId: string;
42
+ }[];
43
+ }, {
44
+ policyBindings?: {
45
+ policyId: string;
46
+ mode?: "monitor" | "enforce" | undefined;
47
+ }[] | undefined;
48
+ slaBindings?: {
49
+ slaId: string;
50
+ }[] | undefined;
51
+ }>>;
52
+ nodes: z.ZodArray<z.ZodObject<{
53
+ id: z.ZodString;
54
+ type: z.ZodString;
55
+ mutation: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
56
+ config: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ type: string;
59
+ id: string;
60
+ mutation: boolean;
61
+ config: Record<string, unknown>;
62
+ }, {
63
+ type: string;
64
+ id: string;
65
+ mutation?: boolean | undefined;
66
+ config?: Record<string, unknown> | undefined;
67
+ }>, "many">;
68
+ edges: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
69
+ from: z.ZodString;
70
+ to: z.ZodString;
71
+ }, "strip", z.ZodTypeAny, {
72
+ from: string;
73
+ to: string;
74
+ }, {
75
+ from: string;
76
+ to: string;
77
+ }>, "many">>>;
78
+ }, "strip", z.ZodTypeAny, {
79
+ governance: {
80
+ policyBindings: {
81
+ policyId: string;
82
+ mode: "monitor" | "enforce";
83
+ }[];
84
+ slaBindings: {
85
+ slaId: string;
86
+ }[];
87
+ };
88
+ nodes: {
89
+ type: string;
90
+ id: string;
91
+ mutation: boolean;
92
+ config: Record<string, unknown>;
93
+ }[];
94
+ edges: {
95
+ from: string;
96
+ to: string;
97
+ }[];
98
+ }, {
99
+ nodes: {
100
+ type: string;
101
+ id: string;
102
+ mutation?: boolean | undefined;
103
+ config?: Record<string, unknown> | undefined;
104
+ }[];
105
+ governance?: {
106
+ policyBindings?: {
107
+ policyId: string;
108
+ mode?: "monitor" | "enforce" | undefined;
109
+ }[] | undefined;
110
+ slaBindings?: {
111
+ slaId: string;
112
+ }[] | undefined;
113
+ } | undefined;
114
+ edges?: {
115
+ from: string;
116
+ to: string;
117
+ }[] | undefined;
118
+ }>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ apiVersion: "driftgate.ai/v1";
121
+ kind: "Workflow";
122
+ metadata: {
123
+ name: string;
124
+ workspace: string;
125
+ };
126
+ spec: {
127
+ governance: {
128
+ policyBindings: {
129
+ policyId: string;
130
+ mode: "monitor" | "enforce";
131
+ }[];
132
+ slaBindings: {
133
+ slaId: string;
134
+ }[];
135
+ };
136
+ nodes: {
137
+ type: string;
138
+ id: string;
139
+ mutation: boolean;
140
+ config: Record<string, unknown>;
141
+ }[];
142
+ edges: {
143
+ from: string;
144
+ to: string;
145
+ }[];
146
+ };
147
+ }, {
148
+ apiVersion: "driftgate.ai/v1";
149
+ kind: "Workflow";
150
+ metadata: {
151
+ name: string;
152
+ workspace: string;
153
+ };
154
+ spec: {
155
+ nodes: {
156
+ type: string;
157
+ id: string;
158
+ mutation?: boolean | undefined;
159
+ config?: Record<string, unknown> | undefined;
160
+ }[];
161
+ governance?: {
162
+ policyBindings?: {
163
+ policyId: string;
164
+ mode?: "monitor" | "enforce" | undefined;
165
+ }[] | undefined;
166
+ slaBindings?: {
167
+ slaId: string;
168
+ }[] | undefined;
169
+ } | undefined;
170
+ edges?: {
171
+ from: string;
172
+ to: string;
173
+ }[] | undefined;
174
+ };
175
+ }>;
176
+ type WorkflowYaml = z.infer<typeof WorkflowYamlSchema>;
177
+
178
+ type CompiledWorkflow = {
179
+ workflow: WorkflowYaml;
180
+ compiledPlan: {
181
+ metadata: WorkflowYaml["metadata"];
182
+ governance: WorkflowYaml["spec"]["governance"];
183
+ nodes: Array<{
184
+ id: string;
185
+ type: string;
186
+ mutation: boolean;
187
+ config: Record<string, unknown>;
188
+ }>;
189
+ edges: Array<{
190
+ from: string;
191
+ to: string;
192
+ }>;
193
+ };
194
+ mutationNodeIds: string[];
195
+ checksum: string;
196
+ };
197
+ declare function compileWorkflowYaml(yamlText: string): CompiledWorkflow;
198
+
199
+ export { type CompiledWorkflow, type WorkflowYaml, WorkflowYamlSchema, compileWorkflowYaml };
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ // src/schema.ts
2
+ import { z } from "zod";
3
+ var MetadataSchema = z.object({
4
+ name: z.string().min(1),
5
+ workspace: z.string().min(1)
6
+ });
7
+ var PolicyBindingSchema = z.object({
8
+ policyId: z.string().min(1),
9
+ mode: z.enum(["monitor", "enforce"]).default("enforce")
10
+ });
11
+ var SlaBindingSchema = z.object({
12
+ slaId: z.string().min(1)
13
+ });
14
+ var NodeSchema = z.object({
15
+ id: z.string().min(1),
16
+ type: z.string().min(1),
17
+ mutation: z.boolean().optional().default(false),
18
+ config: z.record(z.unknown()).optional().default({})
19
+ });
20
+ var EdgeSchema = z.object({
21
+ from: z.string().min(1),
22
+ to: z.string().min(1)
23
+ });
24
+ var WorkflowYamlSchema = z.object({
25
+ apiVersion: z.literal("driftgate.ai/v1"),
26
+ kind: z.literal("Workflow"),
27
+ metadata: MetadataSchema,
28
+ spec: z.object({
29
+ governance: z.object({
30
+ policyBindings: z.array(PolicyBindingSchema).default([]),
31
+ slaBindings: z.array(SlaBindingSchema).default([])
32
+ }).default({ policyBindings: [], slaBindings: [] }),
33
+ nodes: z.array(NodeSchema).min(1),
34
+ edges: z.array(EdgeSchema).optional().default([])
35
+ })
36
+ });
37
+
38
+ // src/compiler.ts
39
+ import { createHash } from "crypto";
40
+ import { parse } from "yaml";
41
+ function stableStringify(value) {
42
+ return JSON.stringify(sortValue(value));
43
+ }
44
+ function sortValue(value) {
45
+ if (Array.isArray(value)) {
46
+ return value.map((item) => sortValue(item));
47
+ }
48
+ if (!value || typeof value !== "object") {
49
+ return value;
50
+ }
51
+ const objectValue = value;
52
+ const sortedKeys = Object.keys(objectValue).sort((left, right) => left.localeCompare(right));
53
+ const next = {};
54
+ for (const key of sortedKeys) {
55
+ next[key] = sortValue(objectValue[key]);
56
+ }
57
+ return next;
58
+ }
59
+ function assertValidReferences(workflow) {
60
+ const nodeIds = new Set(workflow.spec.nodes.map((node) => node.id));
61
+ for (const edge of workflow.spec.edges) {
62
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
63
+ throw new Error(`invalid edge reference: ${edge.from} -> ${edge.to}`);
64
+ }
65
+ }
66
+ }
67
+ function compileWorkflowYaml(yamlText) {
68
+ const parsed = parse(yamlText);
69
+ const workflow = WorkflowYamlSchema.parse(parsed);
70
+ assertValidReferences(workflow);
71
+ const nodes = [...workflow.spec.nodes].sort((left, right) => left.id.localeCompare(right.id));
72
+ const edges = [...workflow.spec.edges].sort((left, right) => {
73
+ const fromOrder = left.from.localeCompare(right.from);
74
+ if (fromOrder !== 0) {
75
+ return fromOrder;
76
+ }
77
+ return left.to.localeCompare(right.to);
78
+ });
79
+ const compiledPlan = {
80
+ metadata: workflow.metadata,
81
+ governance: workflow.spec.governance,
82
+ nodes: nodes.map((node) => ({
83
+ id: node.id,
84
+ type: node.type,
85
+ mutation: Boolean(node.mutation),
86
+ config: node.config ?? {}
87
+ })),
88
+ edges
89
+ };
90
+ const mutationNodeIds = compiledPlan.nodes.filter((node) => node.mutation).map((node) => node.id).sort((left, right) => left.localeCompare(right));
91
+ const checksum = createHash("sha256").update(stableStringify(compiledPlan)).digest("hex");
92
+ return {
93
+ workflow,
94
+ compiledPlan,
95
+ mutationNodeIds,
96
+ checksum
97
+ };
98
+ }
99
+ export {
100
+ WorkflowYamlSchema,
101
+ compileWorkflowYaml
102
+ };
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts","../src/compiler.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst MetadataSchema = z.object({\n name: z.string().min(1),\n workspace: z.string().min(1)\n});\n\nconst PolicyBindingSchema = z.object({\n policyId: z.string().min(1),\n mode: z.enum([\"monitor\", \"enforce\"]).default(\"enforce\")\n});\n\nconst SlaBindingSchema = z.object({\n slaId: z.string().min(1)\n});\n\nconst NodeSchema = z.object({\n id: z.string().min(1),\n type: z.string().min(1),\n mutation: z.boolean().optional().default(false),\n config: z.record(z.unknown()).optional().default({})\n});\n\nconst EdgeSchema = z.object({\n from: z.string().min(1),\n to: z.string().min(1)\n});\n\nexport const WorkflowYamlSchema = z.object({\n apiVersion: z.literal(\"driftgate.ai/v1\"),\n kind: z.literal(\"Workflow\"),\n metadata: MetadataSchema,\n spec: z.object({\n governance: z\n .object({\n policyBindings: z.array(PolicyBindingSchema).default([]),\n slaBindings: z.array(SlaBindingSchema).default([])\n })\n .default({ policyBindings: [], slaBindings: [] }),\n nodes: z.array(NodeSchema).min(1),\n edges: z.array(EdgeSchema).optional().default([])\n })\n});\n\nexport type WorkflowYaml = z.infer<typeof WorkflowYamlSchema>;\n","import { createHash } from \"node:crypto\";\nimport { parse } from \"yaml\";\nimport { WorkflowYamlSchema, type WorkflowYaml } from \"./schema.ts\";\n\nexport type CompiledWorkflow = {\n workflow: WorkflowYaml;\n compiledPlan: {\n metadata: WorkflowYaml[\"metadata\"];\n governance: WorkflowYaml[\"spec\"][\"governance\"];\n nodes: Array<{\n id: string;\n type: string;\n mutation: boolean;\n config: Record<string, unknown>;\n }>;\n edges: Array<{ from: string; to: string }>;\n };\n mutationNodeIds: string[];\n checksum: string;\n};\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => sortValue(item));\n }\n if (!value || typeof value !== \"object\") {\n return value;\n }\n const objectValue = value as Record<string, unknown>;\n const sortedKeys = Object.keys(objectValue).sort((left, right) => left.localeCompare(right));\n const next: Record<string, unknown> = {};\n for (const key of sortedKeys) {\n next[key] = sortValue(objectValue[key]);\n }\n return next;\n}\n\nfunction assertValidReferences(workflow: WorkflowYaml): void {\n const nodeIds = new Set(workflow.spec.nodes.map((node) => node.id));\n for (const edge of workflow.spec.edges) {\n if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {\n throw new Error(`invalid edge reference: ${edge.from} -> ${edge.to}`);\n }\n }\n}\n\nexport function compileWorkflowYaml(yamlText: string): CompiledWorkflow {\n const parsed = parse(yamlText);\n const workflow = WorkflowYamlSchema.parse(parsed);\n assertValidReferences(workflow);\n\n const nodes = [...workflow.spec.nodes].sort((left, right) => left.id.localeCompare(right.id));\n const edges = [...workflow.spec.edges].sort((left, right) => {\n const fromOrder = left.from.localeCompare(right.from);\n if (fromOrder !== 0) {\n return fromOrder;\n }\n return left.to.localeCompare(right.to);\n });\n\n const compiledPlan = {\n metadata: workflow.metadata,\n governance: workflow.spec.governance,\n nodes: nodes.map((node) => ({\n id: node.id,\n type: node.type,\n mutation: Boolean(node.mutation),\n config: node.config ?? {}\n })),\n edges\n };\n\n const mutationNodeIds = compiledPlan.nodes\n .filter((node) => node.mutation)\n .map((node) => node.id)\n .sort((left, right) => left.localeCompare(right));\n\n const checksum = createHash(\"sha256\").update(stableStringify(compiledPlan)).digest(\"hex\");\n\n return {\n workflow,\n compiledPlan,\n mutationNodeIds,\n checksum\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@driftgate/workflow-compiler",
3
+ "version": "0.1.0-rc.1",
4
+ "type": "module",
5
+ "description": "Workflow YAML compiler for DriftGate runtime.",
6
+ "license": "Apache-2.0",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./src/index.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./src/*": "./src/*",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "provenance": true
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/driftgate/driftgate-sdk.git"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup src/index.ts --format esm --target node20 --dts --sourcemap --clean --tsconfig tsconfig.build.json",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "dependencies": {
36
+ "yaml": "^2.8.1",
37
+ "zod": "^3.23.8"
38
+ }
39
+ }
@@ -0,0 +1,64 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { compileWorkflowYaml } from "./compiler.ts";
4
+
5
+ const workflowYaml = `
6
+ apiVersion: driftgate.ai/v1
7
+ kind: Workflow
8
+ metadata:
9
+ name: refund-agent
10
+ workspace: acme-payments
11
+ spec:
12
+ governance:
13
+ policyBindings:
14
+ - policyId: pol_refunds
15
+ mode: enforce
16
+ slaBindings:
17
+ - slaId: sla_default
18
+ nodes:
19
+ - id: fetch_order
20
+ type: http
21
+ config:
22
+ path: /orders/:id
23
+ - id: decide_refund
24
+ type: llm
25
+ - id: issue_refund
26
+ type: http
27
+ mutation: true
28
+ edges:
29
+ - from: fetch_order
30
+ to: decide_refund
31
+ - from: decide_refund
32
+ to: issue_refund
33
+ `;
34
+
35
+ test("compiler is deterministic for stable input", () => {
36
+ const first = compileWorkflowYaml(workflowYaml);
37
+ const second = compileWorkflowYaml(workflowYaml);
38
+ assert.equal(first.checksum, second.checksum);
39
+ assert.deepEqual(first.compiledPlan, second.compiledPlan);
40
+ });
41
+
42
+ test("compiler detects mutation nodes", () => {
43
+ const compiled = compileWorkflowYaml(workflowYaml);
44
+ assert.deepEqual(compiled.mutationNodeIds, ["issue_refund"]);
45
+ });
46
+
47
+ test("compiler rejects invalid edge references", () => {
48
+ assert.throws(() =>
49
+ compileWorkflowYaml(`
50
+ apiVersion: driftgate.ai/v1
51
+ kind: Workflow
52
+ metadata:
53
+ name: broken
54
+ workspace: acme
55
+ spec:
56
+ nodes:
57
+ - id: a
58
+ type: http
59
+ edges:
60
+ - from: a
61
+ to: missing
62
+ `)
63
+ );
64
+ });
@@ -0,0 +1,90 @@
1
+ import { createHash } from "node:crypto";
2
+ import { parse } from "yaml";
3
+ import { WorkflowYamlSchema, type WorkflowYaml } from "./schema.ts";
4
+
5
+ export type CompiledWorkflow = {
6
+ workflow: WorkflowYaml;
7
+ compiledPlan: {
8
+ metadata: WorkflowYaml["metadata"];
9
+ governance: WorkflowYaml["spec"]["governance"];
10
+ nodes: Array<{
11
+ id: string;
12
+ type: string;
13
+ mutation: boolean;
14
+ config: Record<string, unknown>;
15
+ }>;
16
+ edges: Array<{ from: string; to: string }>;
17
+ };
18
+ mutationNodeIds: string[];
19
+ checksum: string;
20
+ };
21
+
22
+ function stableStringify(value: unknown): string {
23
+ return JSON.stringify(sortValue(value));
24
+ }
25
+
26
+ function sortValue(value: unknown): unknown {
27
+ if (Array.isArray(value)) {
28
+ return value.map((item) => sortValue(item));
29
+ }
30
+ if (!value || typeof value !== "object") {
31
+ return value;
32
+ }
33
+ const objectValue = value as Record<string, unknown>;
34
+ const sortedKeys = Object.keys(objectValue).sort((left, right) => left.localeCompare(right));
35
+ const next: Record<string, unknown> = {};
36
+ for (const key of sortedKeys) {
37
+ next[key] = sortValue(objectValue[key]);
38
+ }
39
+ return next;
40
+ }
41
+
42
+ function assertValidReferences(workflow: WorkflowYaml): void {
43
+ const nodeIds = new Set(workflow.spec.nodes.map((node) => node.id));
44
+ for (const edge of workflow.spec.edges) {
45
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
46
+ throw new Error(`invalid edge reference: ${edge.from} -> ${edge.to}`);
47
+ }
48
+ }
49
+ }
50
+
51
+ export function compileWorkflowYaml(yamlText: string): CompiledWorkflow {
52
+ const parsed = parse(yamlText);
53
+ const workflow = WorkflowYamlSchema.parse(parsed);
54
+ assertValidReferences(workflow);
55
+
56
+ const nodes = [...workflow.spec.nodes].sort((left, right) => left.id.localeCompare(right.id));
57
+ const edges = [...workflow.spec.edges].sort((left, right) => {
58
+ const fromOrder = left.from.localeCompare(right.from);
59
+ if (fromOrder !== 0) {
60
+ return fromOrder;
61
+ }
62
+ return left.to.localeCompare(right.to);
63
+ });
64
+
65
+ const compiledPlan = {
66
+ metadata: workflow.metadata,
67
+ governance: workflow.spec.governance,
68
+ nodes: nodes.map((node) => ({
69
+ id: node.id,
70
+ type: node.type,
71
+ mutation: Boolean(node.mutation),
72
+ config: node.config ?? {}
73
+ })),
74
+ edges
75
+ };
76
+
77
+ const mutationNodeIds = compiledPlan.nodes
78
+ .filter((node) => node.mutation)
79
+ .map((node) => node.id)
80
+ .sort((left, right) => left.localeCompare(right));
81
+
82
+ const checksum = createHash("sha256").update(stableStringify(compiledPlan)).digest("hex");
83
+
84
+ return {
85
+ workflow,
86
+ compiledPlan,
87
+ mutationNodeIds,
88
+ checksum
89
+ };
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { WorkflowYamlSchema, type WorkflowYaml } from "./schema.ts";
2
+ export { compileWorkflowYaml, type CompiledWorkflow } from "./compiler.ts";
package/src/schema.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+
3
+ const MetadataSchema = z.object({
4
+ name: z.string().min(1),
5
+ workspace: z.string().min(1)
6
+ });
7
+
8
+ const PolicyBindingSchema = z.object({
9
+ policyId: z.string().min(1),
10
+ mode: z.enum(["monitor", "enforce"]).default("enforce")
11
+ });
12
+
13
+ const SlaBindingSchema = z.object({
14
+ slaId: z.string().min(1)
15
+ });
16
+
17
+ const NodeSchema = z.object({
18
+ id: z.string().min(1),
19
+ type: z.string().min(1),
20
+ mutation: z.boolean().optional().default(false),
21
+ config: z.record(z.unknown()).optional().default({})
22
+ });
23
+
24
+ const EdgeSchema = z.object({
25
+ from: z.string().min(1),
26
+ to: z.string().min(1)
27
+ });
28
+
29
+ export const WorkflowYamlSchema = z.object({
30
+ apiVersion: z.literal("driftgate.ai/v1"),
31
+ kind: z.literal("Workflow"),
32
+ metadata: MetadataSchema,
33
+ spec: z.object({
34
+ governance: z
35
+ .object({
36
+ policyBindings: z.array(PolicyBindingSchema).default([]),
37
+ slaBindings: z.array(SlaBindingSchema).default([])
38
+ })
39
+ .default({ policyBindings: [], slaBindings: [] }),
40
+ nodes: z.array(NodeSchema).min(1),
41
+ edges: z.array(EdgeSchema).optional().default([])
42
+ })
43
+ });
44
+
45
+ export type WorkflowYaml = z.infer<typeof WorkflowYamlSchema>;