@sanoma-ai/workflows 0.1.0 → 0.4.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/README.md +186 -76
- 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 +11 -6
- package/dist/approvals.js +38 -10
- package/dist/approvals.js.map +1 -1
- package/dist/call-workflow.d.ts +16 -0
- package/dist/call-workflow.js +35 -0
- package/dist/call-workflow.js.map +1 -0
- package/dist/call.d.ts +35 -2
- package/dist/call.js +123 -43
- package/dist/call.js.map +1 -1
- package/dist/cedar.d.ts +87 -0
- package/dist/cedar.js +365 -0
- package/dist/cedar.js.map +1 -0
- package/dist/client.d.ts +123 -9
- package/dist/client.js +355 -34
- package/dist/client.js.map +1 -1
- package/dist/collection.d.ts +107 -0
- package/dist/collection.js +306 -0
- package/dist/collection.js.map +1 -0
- package/dist/config.d.ts +42 -14
- package/dist/config.js +109 -28
- package/dist/config.js.map +1 -1
- package/dist/constructors.d.ts +2 -0
- package/dist/constructors.js +5 -0
- package/dist/constructors.js.map +1 -0
- package/dist/credentials.d.ts +25 -0
- package/dist/credentials.js +73 -5
- package/dist/credentials.js.map +1 -1
- package/dist/data.d.ts +2 -0
- package/dist/data.js +3 -0
- package/dist/data.js.map +1 -0
- package/dist/datafile.d.ts +38 -3
- package/dist/datafile.js +166 -13
- package/dist/datafile.js.map +1 -1
- package/dist/db.d.ts +5 -3
- package/dist/db.js +88 -21
- package/dist/db.js.map +1 -1
- package/dist/define.d.ts +10 -4
- package/dist/define.js +1 -5
- package/dist/define.js.map +1 -1
- package/dist/describe.d.ts +54 -8
- package/dist/describe.js +84 -11
- package/dist/describe.js.map +1 -1
- package/dist/direct.d.ts +9 -0
- package/dist/direct.js +11 -0
- package/dist/direct.js.map +1 -0
- package/dist/drift.d.ts +64 -12
- package/dist/drift.js +118 -41
- package/dist/drift.js.map +1 -1
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +30 -1
- package/dist/errors.js.map +1 -1
- package/dist/expectations.d.ts +4 -4
- package/dist/expectations.js +43 -9
- package/dist/expectations.js.map +1 -1
- 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 +13 -5
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +12 -1
- package/dist/ledger.js +18 -11
- package/dist/ledger.js.map +1 -1
- package/dist/lint.d.ts +5 -4
- package/dist/lint.js +24 -7
- package/dist/lint.js.map +1 -1
- package/dist/lockfile.d.ts +36 -0
- package/dist/lockfile.js +69 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/op.d.ts +9 -0
- package/dist/op.js.map +1 -1
- package/dist/outline.d.ts +2 -0
- package/dist/outline.js +1 -1
- package/dist/outline.js.map +1 -1
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +17 -3
- package/dist/paths.js.map +1 -1
- package/dist/person.d.ts +61 -0
- package/dist/person.js +69 -0
- package/dist/person.js.map +1 -0
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/policy.d.ts +51 -55
- package/dist/policy.js +14 -36
- package/dist/policy.js.map +1 -1
- package/dist/resource.d.ts +207 -25
- package/dist/resource.js +184 -30
- package/dist/resource.js.map +1 -1
- package/dist/resources.d.ts +76 -9
- package/dist/resources.js +170 -30
- package/dist/resources.js.map +1 -1
- package/dist/run.d.ts +31 -1
- package/dist/run.js +6 -1
- package/dist/run.js.map +1 -1
- package/dist/sandbox.js +5 -1
- package/dist/sandbox.js.map +1 -1
- package/dist/scenario.d.ts +25 -9
- package/dist/scenario.js +125 -27
- package/dist/scenario.js.map +1 -1
- package/dist/scenarios.d.ts +10 -6
- package/dist/scenarios.js +8 -4
- package/dist/scenarios.js.map +1 -1
- package/dist/schema.d.ts +32 -0
- package/dist/schema.js +225 -0
- package/dist/schema.js.map +1 -0
- package/dist/sdk.d.ts +34 -0
- package/dist/sdk.js +168 -0
- package/dist/sdk.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 +188 -4
- package/dist/shared.js +165 -5
- package/dist/shared.js.map +1 -1
- package/dist/sites.d.ts +52 -0
- package/dist/sites.js +174 -0
- package/dist/sites.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/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/version.js.map +1 -1
- package/dist/worker.d.ts +6 -2
- package/dist/worker.js +37 -8
- package/dist/worker.js.map +1 -1
- package/dist/writer.d.ts +35 -0
- package/dist/writer.js +201 -0
- package/dist/writer.js.map +1 -0
- package/oxlint.json +5 -16
- package/package.json +11 -1
- package/src/apply-steps.ts +110 -0
- package/src/apply.ts +626 -0
- package/src/approvals.ts +49 -13
- package/src/call-workflow.ts +44 -0
- package/src/call.ts +173 -46
- package/src/cedar.ts +493 -0
- package/src/client.ts +450 -49
- package/src/collection.ts +431 -0
- package/src/config.ts +168 -48
- package/src/constructors.ts +4 -0
- package/src/credentials.ts +90 -5
- package/src/data.ts +10 -0
- package/src/datafile.ts +207 -15
- package/src/db.ts +90 -22
- package/src/define.ts +11 -9
- package/src/describe.ts +146 -23
- package/src/direct.ts +15 -0
- package/src/drift.ts +155 -45
- package/src/errors.ts +36 -1
- package/src/expectations.ts +46 -11
- package/src/host.ts +96 -0
- package/src/index.ts +56 -2
- package/src/ledger.ts +35 -17
- package/src/lint.ts +29 -12
- package/src/lockfile.ts +95 -0
- package/src/op.ts +9 -0
- package/src/outline.ts +1 -1
- package/src/paths.ts +18 -3
- package/src/person.ts +75 -0
- package/src/plugin.ts +1 -1
- package/src/policy.ts +59 -63
- package/src/resource.ts +302 -52
- package/src/resources.ts +226 -30
- package/src/run.ts +33 -1
- package/src/sandbox.ts +5 -1
- package/src/scenario.ts +175 -43
- package/src/scenarios.ts +15 -8
- package/src/schema.ts +261 -0
- package/src/sdk.ts +211 -0
- package/src/secret.ts +92 -0
- package/src/shared.ts +279 -6
- package/src/sites.ts +220 -0
- package/src/source.ts +82 -0
- package/src/version.ts +3 -2
- package/src/worker.ts +45 -11
- package/src/writer.ts +225 -0
package/src/person.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineConnector } from "./op.ts";
|
|
3
|
+
import { defineResource } from "./resource.ts";
|
|
4
|
+
|
|
5
|
+
// Who exists in a company: people, agents and groups, declared in data files like any resource,
|
|
6
|
+
// as the runtime's own vendor's types (`sanoma.person`, `sanoma.agent`, `sanoma.group`, imported
|
|
7
|
+
// from "sanoma/resources"). They are `vendorless`: the repo is all there is of them, so they have
|
|
8
|
+
// no driver and no drift. A membership is written on a group only, its members references to the
|
|
9
|
+
// declared people and agents; every agent is in the group `agents` besides. Who a principal is,
|
|
10
|
+
// read from them, is `principalOf` (`shared.ts`).
|
|
11
|
+
|
|
12
|
+
/** An id or a group's name: one word, as a scenario names an approver. */
|
|
13
|
+
const WORD = z.string().regex(/^\S+$/, 'is one word, such as "ada"');
|
|
14
|
+
const NONE = { immutable: [], vendorOwned: [], writeOnly: [] };
|
|
15
|
+
|
|
16
|
+
const person = defineResource({
|
|
17
|
+
vendor: "sanoma",
|
|
18
|
+
type: "person",
|
|
19
|
+
title: "Person",
|
|
20
|
+
identity: "id",
|
|
21
|
+
schema: z.object({
|
|
22
|
+
id: WORD,
|
|
23
|
+
name: z.string().optional(),
|
|
24
|
+
/** Matched first by a sign-in that vouches for an email (GitHub's, its verified public one). */
|
|
25
|
+
email: z.email().optional(),
|
|
26
|
+
/** Their GitHub login, which GitHub sign-in matches, ignoring case, when no email does. */
|
|
27
|
+
github: z
|
|
28
|
+
.string()
|
|
29
|
+
.regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/, "is a GitHub login, such as ada-ortiz")
|
|
30
|
+
.optional(),
|
|
31
|
+
}),
|
|
32
|
+
fields: NONE,
|
|
33
|
+
find: ({ id }) => id,
|
|
34
|
+
vendorless: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const agent = defineResource({
|
|
38
|
+
vendor: "sanoma",
|
|
39
|
+
type: "agent",
|
|
40
|
+
title: "Agent",
|
|
41
|
+
identity: "id",
|
|
42
|
+
schema: z.object({ id: WORD, name: z.string().optional(), onBehalfOf: WORD.optional() }),
|
|
43
|
+
fields: { ...NONE, references: { onBehalfOf: { type: "sanoma.person" } } },
|
|
44
|
+
find: ({ id }) => id,
|
|
45
|
+
vendorless: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const group = defineResource({
|
|
49
|
+
vendor: "sanoma",
|
|
50
|
+
type: "group",
|
|
51
|
+
title: "Group",
|
|
52
|
+
identity: "name",
|
|
53
|
+
schema: z.object({ name: WORD, members: z.array(WORD) }),
|
|
54
|
+
fields: {
|
|
55
|
+
...NONE,
|
|
56
|
+
unordered: ["members"],
|
|
57
|
+
references: { members: { type: ["sanoma.person", "sanoma.agent"] } },
|
|
58
|
+
},
|
|
59
|
+
find: ({ name }) => name,
|
|
60
|
+
vendorless: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What a data file declares the company's people with, imported from "sanoma/resources":
|
|
65
|
+
* `export const ada = sanoma.person({ id: "ada", name: "Ada Ortiz", github: "ada-ortiz" })`,
|
|
66
|
+
* `sanoma.agent({ id: "refund-bot", onBehalfOf: bo })` and
|
|
67
|
+
* `sanoma.group({ name: "finance", members: [ada, refundBot] })`.
|
|
68
|
+
*/
|
|
69
|
+
export const sanoma = { person, agent, group };
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The runtime's own connector of the people types: every config's, so its data files may declare
|
|
73
|
+
* them. Its package is `sanoma`, whose `/resources` entry exports `sanoma`.
|
|
74
|
+
*/
|
|
75
|
+
export const peopleConnector = defineConnector("sanoma", sanoma, { title: "Sanoma", package: "sanoma" });
|
package/src/plugin.ts
CHANGED
|
@@ -18,7 +18,7 @@ const plugin = {
|
|
|
18
18
|
type: "problem",
|
|
19
19
|
docs: {
|
|
20
20
|
description:
|
|
21
|
-
"A data file under resources/ holds only imports, `export const <name> = <vendor>.<type>({ … })` with literal fields, and `export default [ … ]`",
|
|
21
|
+
"A data file under resources/ holds only imports, `export const <name> = <vendor>.<type>({ … })` with literal fields, and `export default [ … ]`; resources/data.ts holds only collections, `collection({ … })` from sanoma/data",
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
24
|
create(context: Context) {
|
package/src/policy.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type ApprovalState, Approver, Covers, type Principal, sameApprover } from "./define.ts";
|
|
1
|
+
import { type ApprovalState, type Approver, type Principal, sameApprover } from "./define.ts";
|
|
3
2
|
import type { Effect, Op } from "./op.ts";
|
|
4
3
|
|
|
5
4
|
/** An operation as a policy sees it: plain data, without its schemas. */
|
|
6
5
|
export type PolicyOp = Pick<Op, "id" | "vendor" | "resource" | "name" | "effect">;
|
|
7
6
|
|
|
8
|
-
/**
|
|
9
|
-
* An operation as a policy sees it, for a `PolicyCall`: what the runtime passes, so a policy
|
|
10
|
-
* test can build a call from the connector, `{ op: policyOpOf(shop.order.refund), ... }`.
|
|
11
|
-
*/
|
|
7
|
+
/** An operation as a policy sees it, for a `PolicyCall`: what the runtime passes. */
|
|
12
8
|
export const policyOpOf = ({ id, vendor, resource, name, effect }: Op): PolicyOp => ({
|
|
13
9
|
id,
|
|
14
10
|
vendor,
|
|
@@ -17,7 +13,7 @@ export const policyOpOf = ({ id, vendor, resource, name, effect }: Op): PolicyOp
|
|
|
17
13
|
effect,
|
|
18
14
|
});
|
|
19
15
|
|
|
20
|
-
/** What a policy sees for each operation a run calls
|
|
16
|
+
/** What a policy sees for each operation a run calls: plain data. */
|
|
21
17
|
export interface PolicyCall {
|
|
22
18
|
op: PolicyOp;
|
|
23
19
|
/** The same as `op.effect`. */
|
|
@@ -33,6 +29,8 @@ export interface PolicyCall {
|
|
|
33
29
|
* A policy request has `requestedBy: "policy"` and the `op` it held.
|
|
34
30
|
*/
|
|
35
31
|
run: { id: string; workflow: string; approvals: readonly ApprovalState[] };
|
|
32
|
+
/** For a collection's `save` or `delete`: the record as it was, when there was one. */
|
|
33
|
+
before?: unknown;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
/**
|
|
@@ -44,88 +42,86 @@ export type Decision =
|
|
|
44
42
|
| { kind: "deny"; reason: string; reasons?: string[] }
|
|
45
43
|
| ApproveDecision;
|
|
46
44
|
|
|
47
|
-
/**
|
|
48
|
-
* Hold the call until the approver decides: a person's id, or `{ group }` for anyone in it.
|
|
49
|
-
* The approval covers the held operation, and `covers` adds others, so a policy can let their
|
|
50
|
-
* later calls through once it is approved.
|
|
51
|
-
*/
|
|
45
|
+
/** Hold the call until the approver decides: a person's id, or `{ group }` for anyone in it. */
|
|
52
46
|
interface ApproveDecision {
|
|
53
47
|
kind: "approve";
|
|
54
48
|
approver: Approver;
|
|
55
49
|
title?: string;
|
|
56
|
-
|
|
50
|
+
reasons?: string[];
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Checks a policy's answer. Parsing also copies it, so nothing else the policy returned is kept,
|
|
63
|
-
* and turns `covers` into op ids.
|
|
64
|
-
*/
|
|
65
|
-
export const DecisionSchema = z.discriminatedUnion(
|
|
66
|
-
"kind",
|
|
67
|
-
[
|
|
68
|
-
z.object({ kind: z.literal("allow"), reasons: ReasonList }),
|
|
69
|
-
z.object({ kind: z.literal("deny"), reason: z.string({ error: "deny needs a reason" }), reasons: ReasonList }),
|
|
70
|
-
z.object({
|
|
71
|
-
kind: z.literal("approve"),
|
|
72
|
-
approver: Approver,
|
|
73
|
-
title: z.string({ error: "the title must be a string" }).optional(),
|
|
74
|
-
covers: Covers,
|
|
75
|
-
}),
|
|
76
|
-
],
|
|
77
|
-
{ error: "not a decision" },
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* A decision as checked and as the ledger records it: `covers` by op id, and the `version` of
|
|
82
|
-
* the policy that made it, when it has one.
|
|
83
|
-
*/
|
|
84
|
-
export type RecordedDecision = z.output<typeof DecisionSchema> & { policyVersion?: string };
|
|
53
|
+
/** A decision as the ledger records it, with the `version` of the policy that made it, when it has one. */
|
|
54
|
+
export type RecordedDecision = Decision & { policyVersion?: string };
|
|
85
55
|
|
|
86
56
|
export const allow = (reasons?: string[]): Decision =>
|
|
87
57
|
reasons === undefined ? { kind: "allow" } : { kind: "allow", reasons };
|
|
88
58
|
export const deny = (reason: string, reasons?: string[]): Decision =>
|
|
89
59
|
reasons === undefined ? { kind: "deny", reason } : { kind: "deny", reason, reasons };
|
|
90
60
|
|
|
91
|
-
/** Holds the call for `approver
|
|
92
|
-
export function approve(approver: Approver, { title,
|
|
61
|
+
/** Holds the call for `approver`; `reasons`, the rules that held it, as `allow` and `deny` take them. */
|
|
62
|
+
export function approve(approver: Approver, { title, reasons }: { title?: string; reasons?: string[] } = {}): Decision {
|
|
93
63
|
return {
|
|
94
64
|
kind: "approve",
|
|
95
65
|
approver,
|
|
96
66
|
...(title === undefined ? {} : { title }),
|
|
97
|
-
...(
|
|
67
|
+
...(reasons === undefined ? {} : { reasons }),
|
|
98
68
|
};
|
|
99
69
|
}
|
|
100
70
|
|
|
101
71
|
/**
|
|
102
|
-
* Decides whether a run may make an operation call: allow it, deny it (the run fails),
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* and a replay reuses it, but a call interrupted before that is decided again. A policy
|
|
107
|
-
* must not have side effects.
|
|
72
|
+
* Decides whether a run may make an operation call: allow it, deny it (the run fails), or hold it
|
|
73
|
+
* until someone approves. A company's is made from its Cedar files (`cedarPolicy`); a repo with
|
|
74
|
+
* none has `allowAll`. Each decision is recorded once made, and a replay reuses it, but a call
|
|
75
|
+
* interrupted before that is decided again.
|
|
108
76
|
*/
|
|
109
|
-
export type Policy = ((call: PolicyCall) => Decision
|
|
110
|
-
/** Names this version of the policy
|
|
77
|
+
export type Policy = ((call: PolicyCall) => Decision) & {
|
|
78
|
+
/** Names this version of the policy: a hash of the Cedar policies' ids and text. */
|
|
111
79
|
readonly version?: string;
|
|
80
|
+
/** What the policy would answer for each request ahead of the call, its input unknown. */
|
|
81
|
+
readonly preview: (requests: readonly PreviewRequest[]) => PreviewAnswer[];
|
|
82
|
+
/**
|
|
83
|
+
* May `actor` start a run of `workflow` with `input` (the action `workflow:<name>`), given the
|
|
84
|
+
* run's `approvals` so far (none before it starts)?
|
|
85
|
+
*/
|
|
86
|
+
readonly start: (
|
|
87
|
+
actor: Principal,
|
|
88
|
+
workflow: string,
|
|
89
|
+
input: unknown,
|
|
90
|
+
approvals?: readonly ApprovalState[],
|
|
91
|
+
) => Decision;
|
|
112
92
|
};
|
|
113
93
|
|
|
114
|
-
/** A
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
94
|
+
/** A question asked ahead of a call: may `principal` take `action` (an operation, or `workflow:<name>` to start one) on `resource` (an operation's target)? */
|
|
95
|
+
export interface PreviewRequest {
|
|
96
|
+
principal: Principal;
|
|
97
|
+
action: { id: string; effect?: Effect };
|
|
98
|
+
resource?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A preview's answer: `depends` when the unknown input decides it. `rule` is the Cedar policy
|
|
103
|
+
* behind a hold, a deny or a dependence; `approver`, a hold's.
|
|
104
|
+
*/
|
|
105
|
+
export interface PreviewAnswer {
|
|
106
|
+
answer: "allow" | "hold" | "deny" | "depends";
|
|
107
|
+
rule?: { id: string; file: string; title?: string; reason?: string };
|
|
108
|
+
approver?: Approver;
|
|
125
109
|
}
|
|
126
110
|
|
|
127
|
-
/**
|
|
128
|
-
|
|
111
|
+
/** A policy that decides every call and start as `decide` does, and previews each as `answer`. */
|
|
112
|
+
const decidingAll = (decide: () => Decision, answer: "allow" | "deny"): Policy =>
|
|
113
|
+
Object.freeze(
|
|
114
|
+
Object.assign(decide, {
|
|
115
|
+
start: decide,
|
|
116
|
+
preview: (requests: readonly PreviewRequest[]): PreviewAnswer[] => requests.map(() => ({ answer })),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/** Allows every operation call and run start: the policy of a repo with no `policies/**\/*.cedar`. */
|
|
121
|
+
export const allowAll: Policy = decidingAll(() => allow(), "allow");
|
|
122
|
+
|
|
123
|
+
/** Refuses every operation call and run start, and previews each as a deny, saying `reason`. */
|
|
124
|
+
export const denyAll = (reason: string): Policy => decidingAll(() => deny(reason), "deny");
|
|
129
125
|
|
|
130
126
|
/**
|
|
131
127
|
* True when an approval in the run was approved, covers the operation, and was addressed to
|