anbaric-state-machine 1.2.2 → 1.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/package.json +3 -3
- package/src/StateMachine.ts +23 -15
- package/src/auditing/AuditorFactory.ts +14 -0
- package/src/auditing/AuditorTransaction.ts +12 -10
- package/src/auditing/ConsoleAuditor.ts +12 -0
- package/src/index.ts +2 -1
- package/src/auditing/Auditor.ts +0 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric-state-machine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "This is the state machine library that one can use to add state management to an application. It will, by default, be locally runnable but can deployed to the Anbaric Cloud via the CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chris@anbaric.ai",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"typescript": "^7.0.2"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"anbaric-cloud": "^1.
|
|
19
|
-
"anbaric-tsapi": "^1.
|
|
18
|
+
"anbaric-cloud": "^1.4.0",
|
|
19
|
+
"anbaric-tsapi": "^1.4.0"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src"
|
package/src/StateMachine.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {Action, Actor, Consumer, Job, JobPersistence, PropertyDefinition, Queue, State} from "anbaric-tsapi";
|
|
1
|
+
import {Action, Actor, Auditor, Consumer, Job, JobPersistence, PropertyDefinition, Queue, State} from "anbaric-tsapi";
|
|
2
2
|
import {JobPersistenceFactory} from "./persistence/JobPersistenceFactory";
|
|
3
3
|
import {QueueFactory} from "./scheduling/QueueFactory";
|
|
4
4
|
import {ConsumerFactory} from "./scheduling/ConsumerFactory";
|
|
5
|
-
import {
|
|
5
|
+
import {Code} from "./actors/Code";
|
|
6
|
+
import {AuditorFactory} from "./auditing/AuditorFactory";
|
|
7
|
+
import {AuditorTransaction} from "./auditing/AuditorTransaction";
|
|
6
8
|
|
|
7
9
|
class StateMachine {
|
|
8
10
|
|
|
@@ -13,8 +15,10 @@ class StateMachine {
|
|
|
13
15
|
private persistence: JobPersistence;
|
|
14
16
|
private queue: Queue;
|
|
15
17
|
private consumer: Consumer;
|
|
18
|
+
private auditor: Auditor;
|
|
19
|
+
private machineActor: Code;
|
|
16
20
|
|
|
17
|
-
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance()) {
|
|
21
|
+
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance(), auditor : Auditor = AuditorFactory.instance()) {
|
|
18
22
|
|
|
19
23
|
this.workflowId = workflowId;
|
|
20
24
|
this.states = new Map(states.map(state => [state.id, state]));
|
|
@@ -22,6 +26,8 @@ class StateMachine {
|
|
|
22
26
|
this.dataSchema = new Map(dataSchema.map(property => [property.id, property]));
|
|
23
27
|
this.persistence = persistence;
|
|
24
28
|
this.queue = queue;
|
|
29
|
+
this.auditor = auditor;
|
|
30
|
+
this.machineActor = new Code(workflowId, "state-machine");
|
|
25
31
|
|
|
26
32
|
this.consumer = ConsumerFactory.instance(queue)
|
|
27
33
|
this.consumer.subscribe(workflowId, jobId => this.progressJob(jobId));
|
|
@@ -35,11 +41,13 @@ class StateMachine {
|
|
|
35
41
|
if (! this.validateProperties(properties ?? new Map(), true)) throw new Error("Invalid properties");
|
|
36
42
|
|
|
37
43
|
if (! this.authorizeActor(actor, job)) {
|
|
38
|
-
|
|
44
|
+
await this.auditor.audit(job.id, actor ?? this.machineActor, "CREATE", "Unauthorized start", null);
|
|
39
45
|
throw new Error("Unauthorized");
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
await this.persistence.save(job);
|
|
49
|
+
await this.auditor.audit(job.id, actor ?? this.machineActor, "CREATE", "Job started",
|
|
50
|
+
Object.fromEntries(job.properties));
|
|
43
51
|
|
|
44
52
|
await this.queue.enqueue(job.id, this.workflowId);
|
|
45
53
|
|
|
@@ -49,14 +57,14 @@ class StateMachine {
|
|
|
49
57
|
async updateJob(jobId : string, properties : Map<string, any>, actor : Actor) : Promise<void> {
|
|
50
58
|
|
|
51
59
|
if (! this.authorizeActor(actor, await this.persistence.retrieve(jobId))) {
|
|
52
|
-
|
|
60
|
+
await this.auditor.audit(jobId, actor, "UPDATE_PROPERTIES", "Unauthorized update", null);
|
|
53
61
|
throw new Error("Unauthorized");
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
if (! this.validateProperties(properties, false)) throw new Error("Invalid properties");
|
|
57
65
|
|
|
58
66
|
await this.persistence.updateProperties(jobId, properties);
|
|
59
|
-
|
|
67
|
+
await this.auditor.audit(jobId, actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(properties));
|
|
60
68
|
|
|
61
69
|
await this.queue.enqueue(jobId, this.workflowId);
|
|
62
70
|
}
|
|
@@ -67,19 +75,19 @@ class StateMachine {
|
|
|
67
75
|
if (! action.predicate(job)) throw new Error("Action predicate unmet");
|
|
68
76
|
|
|
69
77
|
if (! this.authorizeActor(action.actor, job)) {
|
|
70
|
-
|
|
78
|
+
await this.auditor.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Unauthorized update", null);
|
|
71
79
|
throw new Error("Actor not authorized to execute action");
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
const newProperties = await action.run(job);
|
|
75
83
|
|
|
76
84
|
if (! this.validateProperties(newProperties, false)) {
|
|
77
|
-
|
|
85
|
+
await this.auditor.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Invalid properties", Object.fromEntries(newProperties));
|
|
78
86
|
throw new Error("The action generated invalid properties");
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
await this.persistence.updateProperties(jobId, newProperties);
|
|
82
|
-
|
|
90
|
+
await this.auditor.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(newProperties));
|
|
83
91
|
|
|
84
92
|
await this.queue.enqueue(jobId, this.workflowId);
|
|
85
93
|
}
|
|
@@ -100,7 +108,7 @@ class StateMachine {
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
private async progressJob(jobId : string) : Promise<void> {
|
|
103
|
-
const auditTransaction =
|
|
111
|
+
const auditTransaction = new AuditorTransaction(this.auditor);
|
|
104
112
|
let pristine = true;
|
|
105
113
|
|
|
106
114
|
const job = await this.persistence.retrieve(jobId);
|
|
@@ -110,18 +118,18 @@ class StateMachine {
|
|
|
110
118
|
if (! action.predicate(job)) continue;
|
|
111
119
|
|
|
112
120
|
if (! this.authorizeActor(action.actor, job)) {
|
|
113
|
-
auditTransaction.audit(jobId, action.actor, "Unauthorized update", null);
|
|
121
|
+
auditTransaction.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Unauthorized update", null);
|
|
114
122
|
continue;
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
const newProperties = await action.run(job);
|
|
118
126
|
|
|
119
127
|
if (! this.validateProperties(newProperties, false)) {
|
|
120
|
-
auditTransaction.audit(jobId, action.actor, "Invalid properties", Object.fromEntries(newProperties));
|
|
128
|
+
auditTransaction.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Invalid properties", Object.fromEntries(newProperties));
|
|
121
129
|
continue;
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
auditTransaction.audit(jobId, action.actor, "Properties updated", Object.fromEntries(newProperties));
|
|
132
|
+
auditTransaction.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(newProperties));
|
|
125
133
|
newProperties.forEach((value, key) => {
|
|
126
134
|
pristine = false;
|
|
127
135
|
job.properties.set(key, value);
|
|
@@ -131,7 +139,7 @@ class StateMachine {
|
|
|
131
139
|
for (const transition of currentState?.transitions ?? []) {
|
|
132
140
|
if (! this.states.has(transition.to)) continue;
|
|
133
141
|
if (job.transition(transition)) {
|
|
134
|
-
auditTransaction.audit(jobId,
|
|
142
|
+
auditTransaction.audit(jobId, this.machineActor, "CHANGE_STATE", `Transitioned to "${transition.to}"`, null);
|
|
135
143
|
pristine = false;
|
|
136
144
|
break;
|
|
137
145
|
}
|
|
@@ -142,7 +150,7 @@ class StateMachine {
|
|
|
142
150
|
await this.queue.enqueue(job.id, this.workflowId);
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
auditTransaction.flush();
|
|
153
|
+
await auditTransaction.flush();
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
async cleanUp() : Promise<void> {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Auditor} from "anbaric-tsapi";
|
|
2
|
+
import {CloudAuditor} from "anbaric-cloud";
|
|
3
|
+
import {ConsoleAuditor} from "./ConsoleAuditor";
|
|
4
|
+
|
|
5
|
+
const AuditorFactory = {
|
|
6
|
+
|
|
7
|
+
instance() : Auditor {
|
|
8
|
+
if (process.env.ANBARIC_AUDITOR_TYPE === "cloud") return new CloudAuditor();
|
|
9
|
+
return new ConsoleAuditor();
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { AuditorFactory }
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import {Actor} from "anbaric-tsapi";
|
|
2
|
-
import type {Auditor} from "./Auditor";
|
|
1
|
+
import {Actor, AuditChange, Auditor} from "anbaric-tsapi";
|
|
3
2
|
|
|
4
|
-
type
|
|
3
|
+
type BufferedEntry = {
|
|
5
4
|
jobId : string,
|
|
6
|
-
actor : Actor
|
|
5
|
+
actor : Actor,
|
|
6
|
+
change : AuditChange,
|
|
7
7
|
changeDescription : string,
|
|
8
8
|
details : any,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
class AuditorTransaction {
|
|
12
12
|
|
|
13
|
-
private entries = new Array<
|
|
13
|
+
private entries = new Array<BufferedEntry>();
|
|
14
14
|
|
|
15
15
|
constructor(private auditor : Auditor) {}
|
|
16
16
|
|
|
17
|
-
audit(jobId : string, actor : Actor
|
|
18
|
-
this.entries.push({ jobId, actor, changeDescription, details });
|
|
17
|
+
audit(jobId : string, actor : Actor, change : AuditChange, changeDescription : string, details : any) : void {
|
|
18
|
+
this.entries.push({ jobId, actor, change, changeDescription, details });
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
flush() : void {
|
|
22
|
-
this.entries
|
|
23
|
-
this.auditor.audit(entry.jobId, entry.actor, entry.changeDescription, entry.details));
|
|
21
|
+
async flush() : Promise<void> {
|
|
22
|
+
const flushing = this.entries;
|
|
24
23
|
this.entries = [];
|
|
24
|
+
for (const entry of flushing) {
|
|
25
|
+
await this.auditor.audit(entry.jobId, entry.actor, entry.change, entry.changeDescription, entry.details);
|
|
26
|
+
}
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {Actor, AuditChange, Auditor} from "anbaric-tsapi";
|
|
2
|
+
|
|
3
|
+
class ConsoleAuditor implements Auditor {
|
|
4
|
+
|
|
5
|
+
async audit(jobId : string, actor : Actor, change : AuditChange,
|
|
6
|
+
changeDescription : string, details : any) : Promise<void> {
|
|
7
|
+
console.log(`[${jobId}] ${actor.id} ${change} ${changeDescription} ${JSON.stringify(details ?? null)}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { ConsoleAuditor }
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@ export * from "./StateMachine";
|
|
|
2
2
|
export * from "./actors/Agent";
|
|
3
3
|
export * from "./actors/Code";
|
|
4
4
|
export * from "./actors/Human";
|
|
5
|
-
export * from "./auditing/
|
|
5
|
+
export * from "./auditing/AuditorFactory";
|
|
6
6
|
export * from "./auditing/AuditorTransaction";
|
|
7
|
+
export * from "./auditing/ConsoleAuditor";
|
|
7
8
|
export * from "./persistence/InMemoryJobPersistence";
|
|
8
9
|
export * from "./persistence/JobPersistenceFactory";
|
|
9
10
|
export * from "./scheduling/InMemoryQueue";
|
package/src/auditing/Auditor.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import {Actor} from "anbaric-tsapi";
|
|
2
|
-
import {AuditorTransaction} from "./AuditorTransaction";
|
|
3
|
-
|
|
4
|
-
class Auditor {
|
|
5
|
-
|
|
6
|
-
private static singleton? : Auditor;
|
|
7
|
-
|
|
8
|
-
static instance() : Auditor {
|
|
9
|
-
if (!Auditor.singleton) {
|
|
10
|
-
Auditor.singleton = new Auditor();
|
|
11
|
-
}
|
|
12
|
-
return Auditor.singleton;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
audit(jobId : string, actor : Actor | undefined, changeDescription : string, details : any) : void {
|
|
16
|
-
console.log(`[${jobId}] ${actor?.id ?? "anonymous"} ${changeDescription} ${JSON.stringify(details ?? null)}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
transaction() : AuditorTransaction {
|
|
20
|
-
return new AuditorTransaction(this);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export { Auditor }
|