@sanoma-ai/workflows 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/README.md +544 -2
- package/dist/apply-steps.d.ts +6 -0
- package/dist/apply-steps.js +93 -0
- package/dist/apply-steps.js.map +1 -0
- package/dist/apply.d.ts +197 -0
- package/dist/apply.js +470 -0
- package/dist/apply.js.map +1 -0
- package/dist/approvals.d.ts +46 -0
- package/dist/approvals.js +111 -0
- package/dist/approvals.js.map +1 -0
- package/dist/ast.d.ts +57 -0
- package/dist/ast.js +81 -0
- package/dist/ast.js.map +1 -0
- package/dist/call.d.ts +37 -0
- package/dist/call.js +417 -0
- package/dist/call.js.map +1 -0
- package/dist/client.d.ts +203 -0
- package/dist/client.js +457 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +126 -0
- package/dist/config.js +264 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +55 -0
- package/dist/credentials.js +261 -0
- package/dist/credentials.js.map +1 -0
- package/dist/cucumber.d.ts +64 -0
- package/dist/cucumber.js +158 -0
- package/dist/cucumber.js.map +1 -0
- package/dist/datafile.d.ts +71 -0
- package/dist/datafile.js +380 -0
- package/dist/datafile.js.map +1 -0
- package/dist/db.d.ts +58 -0
- package/dist/db.js +179 -0
- package/dist/db.js.map +1 -0
- package/dist/define.d.ts +205 -0
- package/dist/define.js +103 -0
- package/dist/define.js.map +1 -0
- package/dist/describe.d.ts +141 -0
- package/dist/describe.js +135 -0
- package/dist/describe.js.map +1 -0
- package/dist/drift.d.ts +127 -0
- package/dist/drift.js +167 -0
- package/dist/drift.js.map +1 -0
- package/dist/env.d.ts +49 -0
- package/dist/env.js +83 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +88 -0
- package/dist/errors.js +148 -0
- package/dist/errors.js.map +1 -0
- package/dist/expectations.d.ts +26 -0
- package/dist/expectations.js +162 -0
- package/dist/expectations.js.map +1 -0
- package/dist/fake.d.ts +55 -0
- package/dist/fake.js +87 -0
- package/dist/fake.js.map +1 -0
- package/dist/fill.d.ts +9 -0
- package/dist/fill.js +17 -0
- package/dist/fill.js.map +1 -0
- package/dist/host.d.ts +56 -0
- package/dist/host.js +45 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +217 -0
- package/dist/ledger.js +251 -0
- package/dist/ledger.js.map +1 -0
- package/dist/lint.d.ts +32 -0
- package/dist/lint.js +168 -0
- package/dist/lint.js.map +1 -0
- package/dist/lockfile.d.ts +36 -0
- package/dist/lockfile.js +69 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/log.d.ts +24 -0
- package/dist/log.js +83 -0
- package/dist/log.js.map +1 -0
- package/dist/op.d.ts +260 -0
- package/dist/op.js +188 -0
- package/dist/op.js.map +1 -0
- package/dist/outline.d.ts +179 -0
- package/dist/outline.js +506 -0
- package/dist/outline.js.map +1 -0
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +30 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugin.d.ts +28 -0
- package/dist/plugin.js +25 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +114 -0
- package/dist/policy.js +60 -0
- package/dist/policy.js.map +1 -0
- package/dist/resource.d.ts +378 -0
- package/dist/resource.js +430 -0
- package/dist/resource.js.map +1 -0
- package/dist/resources.d.ts +77 -0
- package/dist/resources.js +244 -0
- package/dist/resources.js.map +1 -0
- package/dist/run.d.ts +101 -0
- package/dist/run.js +7 -0
- package/dist/run.js.map +1 -0
- package/dist/sandbox.d.ts +33 -0
- package/dist/sandbox.js +127 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/scenario.d.ts +221 -0
- package/dist/scenario.js +1104 -0
- package/dist/scenario.js.map +1 -0
- package/dist/scenarios.d.ts +55 -0
- package/dist/scenarios.js +71 -0
- package/dist/scenarios.js.map +1 -0
- package/dist/secret.d.ts +42 -0
- package/dist/secret.js +58 -0
- package/dist/secret.js.map +1 -0
- package/dist/shared.d.ts +69 -0
- package/dist/shared.js +84 -0
- package/dist/shared.js.map +1 -0
- package/dist/source.d.ts +58 -0
- package/dist/source.js +54 -0
- package/dist/source.js.map +1 -0
- package/dist/steps.d.ts +76 -0
- package/dist/steps.js +23 -0
- package/dist/steps.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.js +76 -0
- package/dist/version.js.map +1 -0
- package/dist/worker.d.ts +37 -0
- package/dist/worker.js +228 -0
- package/dist/worker.js.map +1 -0
- package/dist/writer.d.ts +35 -0
- package/dist/writer.js +201 -0
- package/dist/writer.js.map +1 -0
- package/oxlint.json +128 -0
- package/package.json +96 -3
- package/src/apply-steps.ts +110 -0
- package/src/apply.ts +626 -0
- package/src/approvals.ts +157 -0
- package/src/ast.ts +115 -0
- package/src/call.ts +483 -0
- package/src/client.ts +604 -0
- package/src/config.ts +404 -0
- package/src/credentials.ts +296 -0
- package/src/cucumber.ts +230 -0
- package/src/datafile.ts +481 -0
- package/src/db.ts +197 -0
- package/src/define.ts +285 -0
- package/src/describe.ts +282 -0
- package/src/drift.ts +259 -0
- package/src/env.ts +122 -0
- package/src/errors.ts +202 -0
- package/src/expectations.ts +191 -0
- package/src/fake.ts +156 -0
- package/src/fill.ts +24 -0
- package/src/host.ts +96 -0
- package/src/index.ts +143 -0
- package/src/ledger.ts +419 -0
- package/src/lint.ts +208 -0
- package/src/lockfile.ts +95 -0
- package/src/log.ts +82 -0
- package/src/op.ts +402 -0
- package/src/outline.ts +620 -0
- package/src/paths.ts +32 -0
- package/src/plugin.ts +37 -0
- package/src/policy.ts +139 -0
- package/src/resource.ts +670 -0
- package/src/resources.ts +311 -0
- package/src/run.ts +106 -0
- package/src/sandbox.ts +161 -0
- package/src/scenario.ts +1365 -0
- package/src/scenarios.ts +111 -0
- package/src/secret.ts +92 -0
- package/src/shared.ts +126 -0
- package/src/source.ts +82 -0
- package/src/steps.ts +90 -0
- package/src/version.ts +86 -0
- package/src/worker.ts +264 -0
- package/src/writer.ts +225 -0
package/dist/writer.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse } from "./ast.js";
|
|
4
|
+
import { isReference, keyName, readDataFile, SECRET_ENTRIES, } from "./datafile.js";
|
|
5
|
+
import { isSecret } from "./secret.js";
|
|
6
|
+
/**
|
|
7
|
+
* oxfmt's options from `<root>/.oxfmtrc.json`, what `oxfmt` itself reads there, less what only
|
|
8
|
+
* its CLI uses (`ignorePatterns`, `overrides`); none, oxfmt's defaults, without the file.
|
|
9
|
+
*/
|
|
10
|
+
export function formatOptions(root) {
|
|
11
|
+
let text;
|
|
12
|
+
try {
|
|
13
|
+
text = readFileSync(join(root, ".oxfmtrc.json"), "utf8");
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err.code === "ENOENT")
|
|
17
|
+
return {};
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
const { $schema: _, ignorePatterns: __, overrides: ___, ...options } = JSON.parse(text);
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `source`, a data file's text, with fields of its resource `exportName` set to new values:
|
|
25
|
+
* `fields` by dotted path, a list item's with its index, as a drift report names them
|
|
26
|
+
* (`required_pull_request_reviews.0.dismiss_stale_reviews`). A field the declaration does not
|
|
27
|
+
* have is added at the end of the object that should hold it. Only that `export const` changes:
|
|
28
|
+
* it is formatted with oxfmt under `options` (`formatOptions`), and every other byte of the file
|
|
29
|
+
* stays as it was.
|
|
30
|
+
*
|
|
31
|
+
* A value naming a secret adds `import { secret } from "sanoma/resources"` after the file's
|
|
32
|
+
* imports when it has none.
|
|
33
|
+
*
|
|
34
|
+
* Throws when the file does not parse, declares no such resource, or a path cannot be set: it
|
|
35
|
+
* runs through a value that is not an object or a list, or ends at a reference to another
|
|
36
|
+
* resource (a person changes a reference; a vendor's value never replaces one).
|
|
37
|
+
*/
|
|
38
|
+
export async function setFields(source, exportName, fields, options = {}) {
|
|
39
|
+
let text = source;
|
|
40
|
+
// One field at a time, each against the file as the last left it: no offset goes stale.
|
|
41
|
+
for (const [path, value] of Object.entries(fields)) {
|
|
42
|
+
const { start, end, replacement } = editAt(declarationOf(text, exportName).object, path, value);
|
|
43
|
+
text = text.slice(0, start) + replacement + text.slice(end);
|
|
44
|
+
}
|
|
45
|
+
if (Object.values(fields).some(holdsSecret))
|
|
46
|
+
text = withSecretImport(text);
|
|
47
|
+
// The declaration on its own: a top-level statement formats the same in its file as alone.
|
|
48
|
+
const { start, end } = declarationOf(text, exportName).statement;
|
|
49
|
+
const formatted = await format(text.slice(start, end), options);
|
|
50
|
+
return text.slice(0, start) + formatted.trimEnd() + text.slice(end);
|
|
51
|
+
}
|
|
52
|
+
/** What a data file imports `secret` from when it names one (`readDataFile` lists no such import). */
|
|
53
|
+
const SECRET_IMPORT = { source: "sanoma/resources", imported: "secret", local: "secret" };
|
|
54
|
+
/**
|
|
55
|
+
* A whole data file: the imports, one per source, then each resource as `export const`, then
|
|
56
|
+
* `export default` naming them all, formatted with oxfmt under `options`. `readDataFile` reads it
|
|
57
|
+
* back as it was given: what `readDataFile` returns renders the file again. A file whose values
|
|
58
|
+
* name a secret also imports `secret` from "sanoma/resources".
|
|
59
|
+
*/
|
|
60
|
+
export async function renderDataFile({ imports, exports }, options = {}) {
|
|
61
|
+
const names = new Map();
|
|
62
|
+
const hasSecret = imports.some((i) => i.local === SECRET_IMPORT.local);
|
|
63
|
+
const all = !hasSecret && exports.some((e) => holdsSecret(e.desired)) ? [...imports, SECRET_IMPORT] : imports;
|
|
64
|
+
for (const { source, imported, local } of all) {
|
|
65
|
+
const list = names.get(source) ?? [];
|
|
66
|
+
list.push(imported === local ? local : `${imported} as ${local}`);
|
|
67
|
+
names.set(source, list);
|
|
68
|
+
}
|
|
69
|
+
const parts = [
|
|
70
|
+
[...names].map(([source, list]) => `import { ${list.join(", ")} } from ${JSON.stringify(source)};`).join("\n"),
|
|
71
|
+
// The top-level object on lines of its own, so oxfmt keeps it expanded as people write it.
|
|
72
|
+
...exports.map(({ name, vendor, type, desired }) => {
|
|
73
|
+
const body = Object.entries(desired).map(([key, value]) => `${keyOf(key)}: ${literal(value)},\n`);
|
|
74
|
+
return `export const ${name} = ${vendor}.${type}({${body.length ? `\n${body.join("")}` : ""}});`;
|
|
75
|
+
}),
|
|
76
|
+
`export default [${exports.map((e) => e.name).join(", ")}];`,
|
|
77
|
+
];
|
|
78
|
+
return format(`${parts.filter(Boolean).join("\n\n")}\n`, options);
|
|
79
|
+
}
|
|
80
|
+
/** `text` with `secret` imported after its imports, unless it imports it already. */
|
|
81
|
+
function withSecretImport(text) {
|
|
82
|
+
const imports = parse("/data-file.ts", text).program.body.filter((s) => s.type === "ImportDeclaration");
|
|
83
|
+
const has = imports.some((s) => SECRET_ENTRIES.has(s.source.value) &&
|
|
84
|
+
s.specifiers.some((sp) => (sp.imported?.name ?? sp.imported?.value) === "secret"));
|
|
85
|
+
if (has)
|
|
86
|
+
return text;
|
|
87
|
+
const at = imports.at(-1)?.end ?? 0;
|
|
88
|
+
const line = `import { ${SECRET_IMPORT.local} } from ${JSON.stringify(SECRET_IMPORT.source)};`;
|
|
89
|
+
return at ? `${text.slice(0, at)}\n${line}${text.slice(at)}` : `${line}\n${text}`;
|
|
90
|
+
}
|
|
91
|
+
/** The `export const` of the resource `name` in `text`, and the object literal its constructor takes. */
|
|
92
|
+
function declarationOf(text, name) {
|
|
93
|
+
// The name is never resolved, and a relative import is checked against the file's own directory.
|
|
94
|
+
const filename = "/data-file.ts";
|
|
95
|
+
const { program, errors } = parse(filename, text);
|
|
96
|
+
if (errors.length)
|
|
97
|
+
throw new Error(`The data file does not parse: ${errors[0].message}`);
|
|
98
|
+
const found = readDataFile(program, filename).exports.find((e) => e.name === name);
|
|
99
|
+
if (!found)
|
|
100
|
+
throw new Error(`The data file declares no resource ${name}`);
|
|
101
|
+
const statement = program.body.find((s) => s.start === found.start);
|
|
102
|
+
return { statement, object: statement.declaration.declarations[0].init.arguments[0] };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What setting `path` to `value` in `object` replaces, and with what. A key may hold a dot
|
|
106
|
+
* (`metadata: { "app.owner": … }`), and a drift report's path does not say so: at each object,
|
|
107
|
+
* the longest run of the path's segments that names a field is that field.
|
|
108
|
+
*/
|
|
109
|
+
function editAt(object, path, value) {
|
|
110
|
+
const keys = path.split(".");
|
|
111
|
+
let node = object;
|
|
112
|
+
for (let i = 0; i < keys.length;) {
|
|
113
|
+
if (node.type === "ObjectExpression") {
|
|
114
|
+
const names = new Map(node.properties.map((p) => [fieldName(p), p]));
|
|
115
|
+
let j = keys.length;
|
|
116
|
+
while (j > i && !names.has(keys.slice(i, j).join(".")))
|
|
117
|
+
j--;
|
|
118
|
+
if (j === i)
|
|
119
|
+
return added(node, keys.slice(i), path, value);
|
|
120
|
+
node = names.get(keys.slice(i, j).join(".")).value;
|
|
121
|
+
i = j;
|
|
122
|
+
}
|
|
123
|
+
else if (node.type === "ArrayExpression" && /^\d+$/.test(keys[i]) && node.elements[Number(keys[i])]) {
|
|
124
|
+
node = node.elements[Number(keys[i])];
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const at = keys.slice(0, i).join(".");
|
|
129
|
+
throw new Error(`Cannot set ${path}: ${at} is not an object or a list with item ${keys[i]}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const named = referenceIn(node);
|
|
133
|
+
if (named) {
|
|
134
|
+
throw new Error(`Cannot set ${path}: it names the resource ${named}; change a reference in the file itself`);
|
|
135
|
+
}
|
|
136
|
+
return { start: node.start, end: node.end, replacement: literal(value) };
|
|
137
|
+
}
|
|
138
|
+
/** The first resource a value names, itself or anywhere inside it: a vendor's value never replaces one. */
|
|
139
|
+
function referenceIn(node) {
|
|
140
|
+
if (node.type === "Identifier")
|
|
141
|
+
return node.name;
|
|
142
|
+
const items = node.type === "ArrayExpression" ? node.elements : node.type === "ObjectExpression" ? node.properties : [];
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
const named = item && referenceIn(item.type === "Property" ? item.value : item);
|
|
145
|
+
if (named)
|
|
146
|
+
return named;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The edit that adds `keys` (the first missing, then any under it) with `value` at the end of
|
|
152
|
+
* `object`. Only objects are made under it: a list item's index there is refused.
|
|
153
|
+
*/
|
|
154
|
+
function added(object, [key, ...under], path, value) {
|
|
155
|
+
const index = under.find((k) => /^\d+$/.test(k));
|
|
156
|
+
if (index !== undefined)
|
|
157
|
+
throw new Error(`Cannot set ${path}: the file has no list to hold item ${index}`);
|
|
158
|
+
const nested = under.reduceRight((v, k) => Object.fromEntries([[k, v]]), value);
|
|
159
|
+
const field = `${keyOf(key)}: ${literal(nested)}`;
|
|
160
|
+
const last = object.properties.at(-1);
|
|
161
|
+
return last
|
|
162
|
+
? { start: last.end, end: last.end, replacement: `, ${field}` }
|
|
163
|
+
: { start: object.start, end: object.end, replacement: `{ ${field} }` };
|
|
164
|
+
}
|
|
165
|
+
/** A property's name, as `readDataFile` reads it. */
|
|
166
|
+
const fieldName = (p) => (p.type === "Property" && !p.computed ? keyName(p.key) : undefined);
|
|
167
|
+
/** A field's name in source: bare when it can be, else a string. */
|
|
168
|
+
const keyOf = (key) => (/^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key));
|
|
169
|
+
/** True when a value names a secret, itself or anywhere inside it. */
|
|
170
|
+
const holdsSecret = (value) => isSecret(value) ||
|
|
171
|
+
(typeof value === "object" && value !== null && !isReference(value) && Object.values(value).some(holdsSecret));
|
|
172
|
+
/** A value in the data-file subset, as source, on one line: oxfmt lays it out. */
|
|
173
|
+
function literal(value) {
|
|
174
|
+
if (isReference(value))
|
|
175
|
+
return value.name;
|
|
176
|
+
if (isSecret(value))
|
|
177
|
+
return `secret(${JSON.stringify(value.$secret)}${value.generate ? ", { generate: true }" : ""})`;
|
|
178
|
+
if (value === null || typeof value === "boolean" || typeof value === "string")
|
|
179
|
+
return JSON.stringify(value);
|
|
180
|
+
if (typeof value === "number") {
|
|
181
|
+
if (!Number.isFinite(value))
|
|
182
|
+
throw new Error(`${value} cannot be written in a data file: write a finite number`);
|
|
183
|
+
return Object.is(value, -0) ? "0" : String(value);
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(value))
|
|
186
|
+
return `[${value.map(literal).join(", ")}]`;
|
|
187
|
+
if (typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
188
|
+
const fields = Object.entries(value).map(([key, v]) => `${keyOf(key)}: ${literal(v)}`);
|
|
189
|
+
return fields.length ? `{ ${fields.join(", ")} }` : "{}";
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`${String(value)} cannot be written in a data file: ${typeof value} is not a data value`);
|
|
192
|
+
}
|
|
193
|
+
/** `text` formatted by oxfmt, loaded only when a file is written. */
|
|
194
|
+
async function format(text, options) {
|
|
195
|
+
const oxfmt = await import("oxfmt");
|
|
196
|
+
const { code, errors } = await oxfmt.format("data-file.ts", text, options);
|
|
197
|
+
if (errors.length)
|
|
198
|
+
throw new Error(`oxfmt could not format the data file: ${errors[0].message}`);
|
|
199
|
+
return code;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"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"}
|
package/oxlint.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"overrides": [
|
|
3
|
+
{
|
|
4
|
+
"files": ["workflows/**", "policies/**"],
|
|
5
|
+
"rules": {
|
|
6
|
+
"no-restricted-globals": [
|
|
7
|
+
"error",
|
|
8
|
+
{
|
|
9
|
+
"name": "Date",
|
|
10
|
+
"message": "Workflows are replayed: use `await ctx.now()` for the time and `ctx.sleep({ until })` to wait for a date."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "fetch",
|
|
14
|
+
"message": "A workflow must call vendors through ctx, so the policy sees every call: use a connector operation listed in `uses`."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "WebSocket",
|
|
18
|
+
"message": "A workflow must call vendors through ctx, so the policy sees every call: use a connector operation listed in `uses`."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "XMLHttpRequest",
|
|
22
|
+
"message": "A workflow must call vendors through ctx, so the policy sees every call: use a connector operation listed in `uses`."
|
|
23
|
+
},
|
|
24
|
+
{ "name": "setTimeout", "message": "Workflows are replayed: use `ctx.sleep`." },
|
|
25
|
+
{ "name": "setInterval", "message": "Workflows are replayed: use `ctx.sleep` in a loop." },
|
|
26
|
+
{ "name": "setImmediate", "message": "Workflows are replayed: use `ctx.sleep`." },
|
|
27
|
+
{ "name": "queueMicrotask", "message": "Workflows are replayed: await the work directly." },
|
|
28
|
+
{ "name": "performance", "message": "Workflows are replayed: use `await ctx.now()`." },
|
|
29
|
+
{
|
|
30
|
+
"name": "process",
|
|
31
|
+
"message": "Workflows are replayed: pass configuration as workflow input; drivers read the environment."
|
|
32
|
+
},
|
|
33
|
+
{ "name": "crypto", "message": "Workflows are replayed: derive ids from `ctx.runId`." },
|
|
34
|
+
{
|
|
35
|
+
"name": "require",
|
|
36
|
+
"message": "Import from `@sanoma-ai/workflows`, a `@sanoma-ai/connector-*` package, zod or a file in this directory."
|
|
37
|
+
},
|
|
38
|
+
{ "name": "eval", "message": "Not allowed in a workflow or policy." },
|
|
39
|
+
{ "name": "Function", "message": "Not allowed in a workflow or policy." },
|
|
40
|
+
{
|
|
41
|
+
"name": "globalThis",
|
|
42
|
+
"message": "Reaching globals through globalThis would bypass these rules: use `ctx`."
|
|
43
|
+
},
|
|
44
|
+
{ "name": "global", "message": "Reaching globals through global would bypass these rules: use `ctx`." }
|
|
45
|
+
],
|
|
46
|
+
"no-restricted-properties": [
|
|
47
|
+
"error",
|
|
48
|
+
{
|
|
49
|
+
"object": "Math",
|
|
50
|
+
"property": "random",
|
|
51
|
+
"message": "Workflows are replayed: derive values from `ctx.runId` or the input."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"object": "Promise",
|
|
55
|
+
"property": "all",
|
|
56
|
+
"message": "Calls through ctx run one at a time; declare a fan-out with `ctx.all([...])` so the ledger and the graph show it."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"object": "Promise",
|
|
60
|
+
"property": "allSettled",
|
|
61
|
+
"message": "Calls through ctx run one at a time; declare a fan-out with `ctx.all([...])` so the ledger and the graph show it."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"object": "Promise",
|
|
65
|
+
"property": "race",
|
|
66
|
+
"message": "Calls through ctx run one at a time, so nothing races: pick one call, or sleep and then check."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"object": "Promise",
|
|
70
|
+
"property": "any",
|
|
71
|
+
"message": "Calls through ctx run one at a time, so nothing races: pick one call, or sleep and then check."
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"no-restricted-imports": [
|
|
75
|
+
"error",
|
|
76
|
+
{
|
|
77
|
+
"paths": [
|
|
78
|
+
{
|
|
79
|
+
"name": "@sanoma-ai/testing",
|
|
80
|
+
"message": "Fakes and test helpers belong in tests. A workflow must call vendors through ctx, so the policy sees every call."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "@sanoma-ai/app",
|
|
84
|
+
"message": "The app starts runs and decides approvals: a workflow could approve itself."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "@sanoma-ai/workflows",
|
|
88
|
+
"allowImportNames": [
|
|
89
|
+
"defineWorkflow",
|
|
90
|
+
"definePolicy",
|
|
91
|
+
"allow",
|
|
92
|
+
"deny",
|
|
93
|
+
"approve",
|
|
94
|
+
"approvedFor",
|
|
95
|
+
"allowAll",
|
|
96
|
+
"mayDecide",
|
|
97
|
+
"errorCode",
|
|
98
|
+
"z"
|
|
99
|
+
],
|
|
100
|
+
"allowTypeImports": true,
|
|
101
|
+
"message": "A workflow or policy imports only these and types from @sanoma-ai/workflows: the rest (SanomaClient, startWorker, the ledger stores, config helpers) could start runs, approve its own approvals, forge the ledger or read credentials. Ask for approval with `ctx.approval`."
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"patterns": [
|
|
105
|
+
{
|
|
106
|
+
"group": [
|
|
107
|
+
"@sanoma-ai/connector-*/fake",
|
|
108
|
+
"@sanoma-ai/connector-*/driver",
|
|
109
|
+
"@sanoma-ai/workflows/describe",
|
|
110
|
+
"@sanoma-ai/workflows/fake",
|
|
111
|
+
"@sanoma-ai/workflows/plugin",
|
|
112
|
+
"@sanoma-ai/workflows/scenario",
|
|
113
|
+
"@sanoma-ai/workflows/scenarios"
|
|
114
|
+
],
|
|
115
|
+
"message": "A workflow must call vendors through ctx, so the policy sees every call: import the connector itself, `@sanoma-ai/connector-<vendor>`."
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"files": ["resources/**"],
|
|
124
|
+
"jsPlugins": ["./dist/plugin.js"],
|
|
125
|
+
"rules": { "sanoma/data-file": "error" }
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanoma-ai/workflows",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Business processes as TypeScript functions, run durably on DBOS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"approvals",
|
|
7
|
+
"business-as-code",
|
|
8
|
+
"dbos",
|
|
9
|
+
"durable-execution",
|
|
10
|
+
"sanoma",
|
|
11
|
+
"workflows"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/Sanoma-AI/sanoma/tree/main/packages/workflows#readme",
|
|
14
|
+
"bugs": "https://github.com/Sanoma-AI/sanoma/issues",
|
|
15
|
+
"license": "FSL-1.1-ALv2",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Sanoma-AI/sanoma.git",
|
|
19
|
+
"directory": "packages/workflows"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src",
|
|
24
|
+
"!src/AGENTS.md",
|
|
25
|
+
"oxlint.json",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./describe": {
|
|
39
|
+
"types": "./dist/describe.d.ts",
|
|
40
|
+
"default": "./dist/describe.js"
|
|
41
|
+
},
|
|
42
|
+
"./fake": {
|
|
43
|
+
"types": "./dist/fake.d.ts",
|
|
44
|
+
"default": "./dist/fake.js"
|
|
45
|
+
},
|
|
46
|
+
"./plugin": {
|
|
47
|
+
"types": "./dist/plugin.d.ts",
|
|
48
|
+
"default": "./dist/plugin.js"
|
|
49
|
+
},
|
|
50
|
+
"./resources": {
|
|
51
|
+
"types": "./dist/secret.d.ts",
|
|
52
|
+
"default": "./dist/secret.js"
|
|
53
|
+
},
|
|
54
|
+
"./scenario": {
|
|
55
|
+
"types": "./dist/scenario.d.ts",
|
|
56
|
+
"default": "./dist/scenario.js"
|
|
57
|
+
},
|
|
58
|
+
"./scenarios": {
|
|
59
|
+
"types": "./dist/scenarios.d.ts",
|
|
60
|
+
"default": "./dist/scenarios.js"
|
|
61
|
+
},
|
|
62
|
+
"./shared": {
|
|
63
|
+
"types": "./dist/shared.d.ts",
|
|
64
|
+
"default": "./dist/shared.js"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@cucumber/cucumber": "13.3.0",
|
|
72
|
+
"@cucumber/cucumber-expressions": "^20.1.0",
|
|
73
|
+
"@cucumber/messages": "^34.2.1",
|
|
74
|
+
"@cucumber/query": "16.1.1",
|
|
75
|
+
"@dbos-inc/dbos-sdk": "^5.2.11",
|
|
76
|
+
"@faker-js/faker": "^10.6.0",
|
|
77
|
+
"oxc-parser": "^0.153.0",
|
|
78
|
+
"oxfmt": "^0.72.0",
|
|
79
|
+
"pg": "^8.23.1",
|
|
80
|
+
"randexp": "^0.5.3",
|
|
81
|
+
"zod-schema-faker": "^2.1.2"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@types/pg": "^8.23.1",
|
|
85
|
+
"zod": "^4.6.5"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"zod": "^4"
|
|
89
|
+
},
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": "24 || >=26"
|
|
92
|
+
},
|
|
93
|
+
"//": "randexp is a dependency only because zod-schema-faker, which makes up scenario inputs, needs it as a peer.",
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "tsc -p tsconfig.build.json",
|
|
96
|
+
"clean": "rm -rf dist",
|
|
97
|
+
"watch": "tsc -p tsconfig.build.json --watch --preserveWatchOutput"
|
|
98
|
+
}
|
|
6
99
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { join, relative, sep } from "node:path";
|
|
2
|
+
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
3
|
+
import type { ApplyDeclared, ApplySteps, PlannedCall } from "./apply.ts";
|
|
4
|
+
import { decide } from "./call.ts";
|
|
5
|
+
import type { Approver } from "./define.ts";
|
|
6
|
+
import { type Binding, type Bindings, diffLockfiles, LOCKFILE, readLockfile, writeLockfile } from "./lockfile.ts";
|
|
7
|
+
import { currentRun, type Run } from "./run.ts";
|
|
8
|
+
import { APPLY_WORKFLOW } from "./shared.ts";
|
|
9
|
+
|
|
10
|
+
// The apply workflow's steps that call no vendor: reading the data files, asking the policy
|
|
11
|
+
// ahead, and writing the lockfile. Here, not in apply.ts, which is held to the workflow lint and
|
|
12
|
+
// imports no DBOS; each reads its run from `currentRun`.
|
|
13
|
+
|
|
14
|
+
/** The name of the step that writes the lockfile and commits it: its output has the bindings it wrote. */
|
|
15
|
+
const WRITE_STEP = "apply:lockfile";
|
|
16
|
+
|
|
17
|
+
/** How many of the latest apply runs are searched for the lockfile one last wrote. */
|
|
18
|
+
const RUNS_SEARCHED = 100;
|
|
19
|
+
|
|
20
|
+
const runNow = (): Run => {
|
|
21
|
+
const run = currentRun.getStore();
|
|
22
|
+
if (!run) throw new Error("An apply step ran outside its run");
|
|
23
|
+
return run;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The bindings the latest apply run that wrote the lockfile wrote last, from its steps; undefined
|
|
28
|
+
* when none of the latest `RUNS_SEARCHED` did. A sandbox run writes none.
|
|
29
|
+
*/
|
|
30
|
+
async function lastWritten(appName: string): Promise<Bindings | undefined> {
|
|
31
|
+
const runs = await DBOS.listWorkflows({
|
|
32
|
+
workflowName: APPLY_WORKFLOW,
|
|
33
|
+
applicationName: appName,
|
|
34
|
+
sortDesc: true,
|
|
35
|
+
limit: RUNS_SEARCHED,
|
|
36
|
+
loadInput: false,
|
|
37
|
+
loadOutput: false,
|
|
38
|
+
});
|
|
39
|
+
for (const { workflowID } of runs) {
|
|
40
|
+
const steps = await DBOS.listWorkflowSteps(workflowID);
|
|
41
|
+
const last = steps?.findLast((step) => step.name === WRITE_STEP && !step.error);
|
|
42
|
+
if (last) return (last.output as { bindings: Bindings }).bindings;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The apply workflow's steps for a config: `read`, the data files and the lockfile as drift reads
|
|
49
|
+
* them, under `root`; `appName` scopes the search for the lockfile apply last wrote.
|
|
50
|
+
*/
|
|
51
|
+
export function applySteps(
|
|
52
|
+
appName: string,
|
|
53
|
+
root: string | undefined,
|
|
54
|
+
read: () => Promise<Omit<ApplyDeclared, "rebinds">>,
|
|
55
|
+
): ApplySteps {
|
|
56
|
+
return {
|
|
57
|
+
// The data files, and the lockfile against the one apply last wrote: a change someone else made.
|
|
58
|
+
declared: () =>
|
|
59
|
+
DBOS.runStep(
|
|
60
|
+
async () => {
|
|
61
|
+
const declared = await read();
|
|
62
|
+
const before = await lastWritten(appName);
|
|
63
|
+
return { ...declared, rebinds: before ? diffLockfiles(before, declared.bindings) : [] };
|
|
64
|
+
},
|
|
65
|
+
{ name: "apply:data-files" },
|
|
66
|
+
),
|
|
67
|
+
// The policy asked as each call would ask it, now, so one approval can cover them all.
|
|
68
|
+
holds: (calls: PlannedCall[]) =>
|
|
69
|
+
DBOS.runStep(
|
|
70
|
+
async (): Promise<Approver | undefined> => {
|
|
71
|
+
const run = runNow();
|
|
72
|
+
for (const { op: id, input } of calls) {
|
|
73
|
+
const op = run.state.ops.get(id)!;
|
|
74
|
+
// Parsed as the call will be, so the policy sees the same input with its defaults.
|
|
75
|
+
const parsed = op.input.safeParse(input);
|
|
76
|
+
const decision = await decide(run, op, parsed.success ? parsed.data : input);
|
|
77
|
+
if (decision.kind === "approve") return decision.approver;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
},
|
|
81
|
+
{ name: "apply:policy" },
|
|
82
|
+
),
|
|
83
|
+
// The one binding changed on the lockfile as it is now, written whole, then committed, in one
|
|
84
|
+
// step whose output is what it wrote. A sandbox run keeps its lockfile in memory: the run's
|
|
85
|
+
// report has it.
|
|
86
|
+
write: async (key: string, binding: Binding | undefined, message: string) => {
|
|
87
|
+
const run = runNow();
|
|
88
|
+
if (run.sandbox !== undefined || root === undefined) return { written: false };
|
|
89
|
+
const { source } = run.state;
|
|
90
|
+
const { revision } = await DBOS.runStep(
|
|
91
|
+
async (): Promise<{ bindings: Bindings; revision?: string }> => {
|
|
92
|
+
const now = readLockfile(root);
|
|
93
|
+
if (now.problems.length) throw new Error(`${LOCKFILE} cannot be updated: ${now.problems[0]!.message}`);
|
|
94
|
+
const { [key]: _, ...others } = now.bindings;
|
|
95
|
+
const bindings = binding ? { ...others, [key]: binding } : others;
|
|
96
|
+
writeLockfile(root, bindings);
|
|
97
|
+
const paths = [
|
|
98
|
+
relative(source?.root ?? root, join(root, LOCKFILE))
|
|
99
|
+
.split(sep)
|
|
100
|
+
.join("/"),
|
|
101
|
+
];
|
|
102
|
+
const committed = await source?.commit({ paths, message, author: run.actor.id });
|
|
103
|
+
return { bindings, ...(committed && { revision: committed.revision }) };
|
|
104
|
+
},
|
|
105
|
+
{ name: WRITE_STEP },
|
|
106
|
+
);
|
|
107
|
+
return { written: true, ...(revision !== undefined && { revision }) };
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|