@akterjs/conformance 0.0.0-stage → 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +52 -2
  3. package/dist/client-contract.suite.d.mts +23 -0
  4. package/dist/client-contract.suite.mjs +279 -0
  5. package/dist/client-target.errors.d.mts +8 -0
  6. package/dist/client-target.errors.mjs +4 -0
  7. package/dist/engine-conformance.suite.d.mts +19 -0
  8. package/dist/engine-conformance.suite.mjs +357 -0
  9. package/dist/fork/fork-conformance.suite.d.mts +20 -0
  10. package/dist/fork/fork-conformance.suite.mjs +167 -0
  11. package/dist/guarantee-titles.pure.d.mts +31 -0
  12. package/dist/guarantee-titles.pure.mjs +32 -0
  13. package/dist/ledger/ledger-calls.pure.d.mts +79 -0
  14. package/dist/ledger/ledger-calls.pure.mjs +111 -0
  15. package/dist/ledger/ledger-checks.pure.d.mts +21 -0
  16. package/dist/ledger/ledger-checks.pure.mjs +37 -0
  17. package/dist/ledger/ledger.actor.d.mts +13 -0
  18. package/dist/ledger/ledger.actor.mjs +115 -0
  19. package/dist/ledger/ledger.contract.d.mts +310 -0
  20. package/dist/ledger/ledger.contract.mjs +112 -0
  21. package/dist/ledger/plain-ledger.actor.d.mts +4 -0
  22. package/dist/ledger/plain-ledger.actor.mjs +214 -0
  23. package/dist/rejected.errors.d.mts +8 -0
  24. package/dist/rejected.errors.mjs +4 -0
  25. package/dist/tally-clients.layer.d.mts +39 -0
  26. package/dist/tally-clients.layer.mjs +97 -0
  27. package/dist/tally.actor.d.mts +49 -0
  28. package/dist/tally.actor.mjs +26 -0
  29. package/dist/tally.contract.d.mts +51 -0
  30. package/dist/tally.contract.mjs +28 -0
  31. package/dist/workbench/computer-conformance.suite.d.mts +15 -0
  32. package/dist/workbench/computer-conformance.suite.mjs +330 -0
  33. package/dist/workbench/workbench-calls.pure.d.mts +45 -0
  34. package/dist/workbench/workbench-calls.pure.mjs +75 -0
  35. package/dist/workbench/workbench.actor.d.mts +4 -0
  36. package/dist/workbench/workbench.actor.mjs +107 -0
  37. package/dist/workbench/workbench.contract.d.mts +220 -0
  38. package/dist/workbench/workbench.contract.mjs +54 -0
  39. package/package.json +86 -3
  40. package/src/client-contract.suite.ts +413 -0
  41. package/src/client-target.errors.ts +7 -0
  42. package/src/engine-conformance.suite.ts +484 -0
  43. package/src/fork/fork-conformance.suite.ts +204 -0
  44. package/src/guarantee-titles.pure.ts +50 -0
  45. package/src/ledger/ledger-calls.pure.ts +179 -0
  46. package/src/ledger/ledger-checks.pure.ts +84 -0
  47. package/src/ledger/ledger.actor.ts +196 -0
  48. package/src/ledger/ledger.contract.ts +76 -0
  49. package/src/ledger/plain-ledger.actor.ts +246 -0
  50. package/src/rejected.errors.ts +6 -0
  51. package/src/tally-clients.layer.ts +221 -0
  52. package/src/tally.actor.ts +28 -0
  53. package/src/tally.contract.ts +22 -0
  54. package/src/workbench/computer-conformance.suite.ts +380 -0
  55. package/src/workbench/workbench-calls.pure.ts +92 -0
  56. package/src/workbench/workbench.actor.ts +175 -0
  57. package/src/workbench/workbench.contract.ts +47 -0
@@ -0,0 +1,45 @@
1
+ import { Host, Unexpected } from "../ledger/ledger-calls.pure.mjs";
2
+ import { ComputerOp } from "./workbench.contract.mjs";
3
+ import { Effect } from "effect";
4
+ /** The Workbench the computer guarantees run on, whose key every engine under test serves. */
5
+ declare const bench = "bench-1";
6
+ /**
7
+ * The key a fork of `bench` goes into. Akter Cloud forks only into a key on the parent's own shard, and `bench-250`
8
+ * hashes to `bench-1`'s shard there.
9
+ */
10
+ declare const forkedBench = "bench-250";
11
+ declare const ensure: (computer: string) => ComputerOp;
12
+ declare const get: (computer: string) => ComputerOp;
13
+ declare const remove: (computer: string) => ComputerOp;
14
+ declare const stop: (computer: string) => ComputerOp;
15
+ declare const list: ComputerOp;
16
+ /** Starts the calls in one `run` job on the Workbench at `key` and answers the job's id. */
17
+ declare const startedRun: (host: Host, { commandId, key, ops }: {
18
+ readonly commandId: string;
19
+ readonly key: string;
20
+ readonly ops: readonly ComputerOp[];
21
+ }) => Effect.Effect<string, Unexpected>;
22
+ declare const unfinished = "the job had not finished";
23
+ /** What each call of a job answered, once its result has reached the actor. */
24
+ declare const outcomesOf: (host: Host, key: string, job: string) => Effect.Effect<readonly string[], Unexpected>;
25
+ /**
26
+ * Runs the calls in one `run` job on the Workbench at `key` and answers what each returned, once the job's result has
27
+ * reached the actor. A call is one activity, so `run` with the same command id after a restart answers the same job.
28
+ */
29
+ declare const ranAt: (host: Host, key: string, { commandId, ops }: {
30
+ readonly commandId: string;
31
+ readonly ops: readonly ComputerOp[];
32
+ }) => Effect.Effect<readonly string[], Unexpected>;
33
+ /** Runs the calls in one `run` job on the suite's Workbench, `bench`, as `ranAt` does. */
34
+ declare const ran: (host: Host, commandId: string, ops: readonly ComputerOp[]) => Effect.Effect<readonly string[], Unexpected>;
35
+ /** A step on `computer` under `key`, outside the job's journal. */
36
+ declare const exec: (computer: string, key: string, command: string) => ComputerOp;
37
+ /** The calls, as one namespace: each takes the host first, and `exec`, which builds a step to run. */
38
+ declare const workbenchHost: {
39
+ exec: typeof exec;
40
+ outcomesOf: typeof outcomesOf;
41
+ ran: typeof ran;
42
+ ranAt: typeof ranAt;
43
+ startedRun: typeof startedRun;
44
+ };
45
+ export { bench, ensure, forkedBench, get, list, remove, stop, unfinished, workbenchHost };
@@ -0,0 +1,75 @@
1
+ import { ada, nothing } from "../ledger/ledger-calls.pure.mjs";
2
+ import { decodeReply } from "../ledger/ledger-checks.pure.mjs";
3
+ import { ComputerOp, Workbench } from "./workbench.contract.mjs";
4
+ import { Effect, Option, Record, Schema } from "effect";
5
+ // src/workbench/workbench-calls.pure.ts
6
+ /** The Workbench the computer guarantees run on, whose key every engine under test serves. */
7
+ const bench = "bench-1";
8
+ /**
9
+ * The key a fork of `bench` goes into. Akter Cloud forks only into a key on the parent's own shard, and `bench-250`
10
+ * hashes to `bench-1`'s shard there.
11
+ */
12
+ const forkedBench = "bench-250";
13
+ const encodeOps = Schema.encodeEffect(Schema.fromJsonString(Workbench.commands.run.payload));
14
+ const { Ensure, Exec, Get, List, Remove, Stop } = ComputerOp.cases;
15
+ const ensure = (computer) => Ensure.make({ computer });
16
+ const get = (computer) => Get.make({ computer });
17
+ const remove = (computer) => Remove.make({ computer });
18
+ const stop = (computer) => Stop.make({ computer });
19
+ const list = List.make({});
20
+ /** Starts the calls in one `run` job on the Workbench at `key` and answers the job's id. */
21
+ const startedRun = (host, { commandId, key, ops }) => Effect.gen(function* startRun() {
22
+ const payload = yield* encodeOps({ ops });
23
+ const sent = yield* host.command({
24
+ actor: "Workbench",
25
+ commandId,
26
+ key,
27
+ member: "run",
28
+ payload,
29
+ principal: ada
30
+ });
31
+ return yield* Effect.flatten(decodeReply(Workbench.commands.run, sent));
32
+ });
33
+ const unfinished = "the job had not finished";
34
+ /** What each call of a job answered, once its result has reached the actor. */
35
+ const outcomesOf = (host, key, job) => Effect.gen(function* readOutcomes() {
36
+ yield* host.drain;
37
+ const reply = yield* host.query({
38
+ actor: "Workbench",
39
+ key,
40
+ member: "runs",
41
+ payload: nothing,
42
+ principal: ada
43
+ });
44
+ const runs = yield* Effect.flatten(decodeReply(Workbench.queries.runs, reply));
45
+ return Option.getOrElse(Record.get(runs, job), () => [unfinished]);
46
+ });
47
+ /**
48
+ * Runs the calls in one `run` job on the Workbench at `key` and answers what each returned, once the job's result has
49
+ * reached the actor. A call is one activity, so `run` with the same command id after a restart answers the same job.
50
+ */
51
+ const ranAt = (host, key, { commandId, ops }) => Effect.flatMap(startedRun(host, {
52
+ commandId,
53
+ key,
54
+ ops
55
+ }), (job) => outcomesOf(host, key, job));
56
+ /** Runs the calls in one `run` job on the suite's Workbench, `bench`, as `ranAt` does. */
57
+ const ran = (host, commandId, ops) => ranAt(host, bench, {
58
+ commandId,
59
+ ops
60
+ });
61
+ /** A step on `computer` under `key`, outside the job's journal. */
62
+ const exec = (computer, key, command) => Exec.make({
63
+ command,
64
+ computer,
65
+ key
66
+ });
67
+ /** The calls, as one namespace: each takes the host first, and `exec`, which builds a step to run. */
68
+ const workbenchHost = {
69
+ exec,
70
+ outcomesOf,
71
+ ran,
72
+ ranAt,
73
+ startedRun
74
+ };
75
+ export { bench, ensure, forkedBench, get, list, remove, stop, unfinished, workbenchHost };
@@ -0,0 +1,4 @@
1
+ import { Layer } from "effect";
2
+ /** The Workbench and its job on the engine's job runtime, whose activities reach the computers the engine bound. */
3
+ declare const WorkbenchLive: Layer.Layer<import("@akterjs/effect/contract").ActorHandlers<"Workbench">, never, never>;
4
+ export { WorkbenchLive };
@@ -0,0 +1,107 @@
1
+ import { ComputerOp, Workbench } from "./workbench.contract.mjs";
2
+ import { ComputerLimitError } from "@akterjs/effect/contract/errors";
3
+ import { Array, Cause, Effect, Exit, Layer, Option, Predicate, Record, Schema, String as String$1 } from "effect";
4
+ import { Akter } from "@akterjs/effect/contract";
5
+ import { Akter as Akter$1 } from "@akterjs/effect";
6
+ import { Activity } from "effect/workflow";
7
+ import { ComputerName, ExecRan } from "@akterjs/effect/contract/computer";
8
+ // src/workbench/workbench.actor.ts
9
+ const memoryMib = 1024;
10
+ /** Every failure a computer call can answer, by tag. */
11
+ const failures = [
12
+ "ActorUnavailableError",
13
+ "CallerNotAllowedError",
14
+ "ComputerHeldError",
15
+ "ComputerLostError",
16
+ "ComputerNotFoundError",
17
+ "ComputersUnavailableError",
18
+ "InvalidRequestError",
19
+ "SecretUnavailableError",
20
+ "UnauthenticatedError"
21
+ ];
22
+ const isLimit = Schema.is(ComputerLimitError);
23
+ const isRan = Schema.is(ExecRan);
24
+ const specOf = (computer) => ({
25
+ egress: [],
26
+ image: "node:24-bookworm",
27
+ name: ComputerName.make(computer),
28
+ size: {
29
+ memoryMib,
30
+ vcpu: 1
31
+ }
32
+ });
33
+ const stepOf = (result) => {
34
+ if (!isRan(result)) return "unknown";
35
+ return `exit ${Option.getOrElse(Option.map(Option.fromUndefinedOr(result.exitCode), String), () => "none")}: ${result.stdout}`;
36
+ };
37
+ /** An engine may answer before the machine is up, so `starting` and `running` read the same: `up`. */
38
+ const upStates = ["running", "starting"];
39
+ const stateOf = ({ state }) => {
40
+ if (Array.contains(upStates, state)) return "up";
41
+ return state;
42
+ };
43
+ const holdOf = (hold) => Record.getSomes({ hold: Option.fromUndefinedOr(hold) });
44
+ const namesOf = (computers) => `[${Array.join(Array.sort(Array.map(computers, ({ name }) => name), String$1.Order), ",")}]`;
45
+ /** What one call answered, as the suite compares it: a state, a step's exit and output, names, or `removed`. */
46
+ const called = (client, op) => ComputerOp.match(op, {
47
+ Ensure: ({ computer }) => Effect.map(client.ensure(specOf(computer)), stateOf),
48
+ Exec: ({ command, computer, key }) => Effect.map(client.exec(computer, {
49
+ command,
50
+ key
51
+ }), stepOf),
52
+ Get: ({ computer }) => Effect.map(client.get(computer), stateOf),
53
+ List: () => Effect.map(client.list, namesOf),
54
+ Pause: ({ computer, hold }) => Effect.map(client.pause(computer, holdOf(hold)), stateOf),
55
+ Remove: ({ computer }) => Effect.as(client.remove(computer), "removed"),
56
+ Resume: ({ computer, hold }) => Effect.map(client.resume(computer, holdOf(hold)), stateOf),
57
+ Step: () => Effect.die("a durable step runs in the job, not in an activity"),
58
+ Stop: ({ computer }) => Effect.map(client.stop(computer), stateOf)
59
+ });
60
+ /** A failure by its tag; a limit with the limit and its maximum, which the suite checks against what was made. */
61
+ const failureOf = (failure) => {
62
+ if (isLimit(failure)) return `ComputerLimitError ${failure.limit} ${String(failure.maximum)}`;
63
+ return Option.getOrElse(Array.findFirst(failures, (tag) => Predicate.isTagged(failure, tag)), () => "unexpected failure");
64
+ };
65
+ const attempted = (key, op) => Effect.flatMap(Workbench.computer(key), (client) => called(client, op));
66
+ const answered = (key, op) => Effect.match(attempted(key, op), {
67
+ onFailure: failureOf,
68
+ onSuccess: (outcome) => outcome
69
+ });
70
+ const isStep = Schema.is(ComputerOp.cases.Step);
71
+ const timeoutOf = (timeoutSeconds) => Record.getSomes({ timeoutSeconds: Option.fromUndefinedOr(timeoutSeconds) });
72
+ /** A durable step, made by the job itself: it journals its own start and outcome. */
73
+ const stepped = (key, { command, computer, name, timeoutSeconds }) => Effect.match(Effect.flatMap(Workbench.computer(key), (computers) => computers.step(computer, {
74
+ command,
75
+ name,
76
+ ...timeoutOf(timeoutSeconds)
77
+ })), {
78
+ onFailure: failureOf,
79
+ onSuccess: stepOf
80
+ });
81
+ /**
82
+ * Makes each call in its own activity, so a job resumed after a restart reads back the calls it already made, and each
83
+ * `Step` as a durable step of the job.
84
+ */
85
+ const RunLive = Workbench.jobs.run.toLayer(({ key, ops }) => Effect.forEach(ops, (op, index) => {
86
+ if (isStep(op)) return stepped(key, op);
87
+ return Activity.make({
88
+ execute: answered(key, op),
89
+ name: `call-${String(index)}`,
90
+ success: Schema.String
91
+ });
92
+ }));
93
+ const outcomesOf = (result) => Exit.match(result, {
94
+ onFailure: (cause) => [`job failed: ${Cause.pretty(cause)}`],
95
+ onSuccess: (outcomes) => outcomes
96
+ });
97
+ const WorkbenchHandlers = Workbench.toLayer({
98
+ commands: { run: (_bench, { ops }) => Effect.flatMap(Akter.actorKey, (key) => Akter.start(Workbench.jobs.run, {
99
+ key,
100
+ ops
101
+ })) },
102
+ queries: { runs: (bench) => Effect.succeed(bench.runs) },
103
+ results: { run: (bench, result, job) => Workbench.update({ runs: Record.set(bench.runs, job.id, outcomesOf(result)) }) }
104
+ });
105
+ /** The Workbench and its job on the engine's job runtime, whose activities reach the computers the engine bound. */
106
+ const WorkbenchLive = Layer.merge(WorkbenchHandlers, RunLive).pipe(Layer.provide(Akter$1.jobs));
107
+ export { WorkbenchLive };
@@ -0,0 +1,220 @@
1
+ import { Schema } from "effect";
2
+ /** One call a `run` job makes to the actor's computers, in order. */
3
+ declare const ComputerOp: Schema.TaggedUnion<{
4
+ readonly Ensure: Schema.TaggedStruct<"Ensure", {
5
+ computer: Schema.String;
6
+ }>;
7
+ readonly Exec: Schema.TaggedStruct<"Exec", {
8
+ readonly computer: Schema.String;
9
+ readonly command: Schema.String;
10
+ readonly key: Schema.String;
11
+ }>;
12
+ readonly Get: Schema.TaggedStruct<"Get", {
13
+ computer: Schema.String;
14
+ }>;
15
+ readonly List: Schema.TaggedStruct<"List", {}>;
16
+ readonly Pause: Schema.TaggedStruct<"Pause", {
17
+ computer: Schema.String;
18
+ hold: Schema.optionalKey<Schema.String>;
19
+ }>;
20
+ readonly Remove: Schema.TaggedStruct<"Remove", {
21
+ computer: Schema.String;
22
+ }>;
23
+ readonly Resume: Schema.TaggedStruct<"Resume", {
24
+ computer: Schema.String;
25
+ hold: Schema.optionalKey<Schema.String>;
26
+ }>;
27
+ readonly Step: Schema.TaggedStruct<"Step", {
28
+ readonly computer: Schema.String;
29
+ readonly command: Schema.String;
30
+ readonly name: Schema.String;
31
+ readonly timeoutSeconds: Schema.optionalKey<Schema.Int>;
32
+ }>;
33
+ readonly Stop: Schema.TaggedStruct<"Stop", {
34
+ computer: Schema.String;
35
+ }>;
36
+ }>;
37
+ type ComputerOp = typeof ComputerOp.Type;
38
+ /**
39
+ * The suite's actor for computer guarantees. `run` starts a job that makes its calls to the actor's computers, each
40
+ * in an activity or, for a `Step`, as a durable step of the job, and records what each answered; `runs` reads those
41
+ * records back.
42
+ */
43
+ declare const Workbench: import("@akterjs/effect/contract").Actor<"Workbench", import("@akterjs/effect/contract").DefinitionOf<{
44
+ readonly commands: {
45
+ run: import("@akterjs/effect/contract").Command<Schema.Struct<{
46
+ readonly ops: Schema.$Array<Schema.TaggedUnion<{
47
+ readonly Ensure: Schema.TaggedStruct<"Ensure", {
48
+ computer: Schema.String;
49
+ }>;
50
+ readonly Exec: Schema.TaggedStruct<"Exec", {
51
+ readonly computer: Schema.String;
52
+ readonly command: Schema.String;
53
+ readonly key: Schema.String;
54
+ }>;
55
+ readonly Get: Schema.TaggedStruct<"Get", {
56
+ computer: Schema.String;
57
+ }>;
58
+ readonly List: Schema.TaggedStruct<"List", {}>;
59
+ readonly Pause: Schema.TaggedStruct<"Pause", {
60
+ computer: Schema.String;
61
+ hold: Schema.optionalKey<Schema.String>;
62
+ }>;
63
+ readonly Remove: Schema.TaggedStruct<"Remove", {
64
+ computer: Schema.String;
65
+ }>;
66
+ readonly Resume: Schema.TaggedStruct<"Resume", {
67
+ computer: Schema.String;
68
+ hold: Schema.optionalKey<Schema.String>;
69
+ }>;
70
+ readonly Step: Schema.TaggedStruct<"Step", {
71
+ readonly computer: Schema.String;
72
+ readonly command: Schema.String;
73
+ readonly name: Schema.String;
74
+ readonly timeoutSeconds: Schema.optionalKey<Schema.Int>;
75
+ }>;
76
+ readonly Stop: Schema.TaggedStruct<"Stop", {
77
+ computer: Schema.String;
78
+ }>;
79
+ }>>;
80
+ }>, Schema.String, Schema.Never, never>;
81
+ };
82
+ readonly initial: () => {
83
+ runs: {};
84
+ };
85
+ readonly jobs: {
86
+ run: import("@akterjs/effect/contract").Job<{
87
+ readonly key: Schema.String;
88
+ readonly ops: Schema.$Array<Schema.TaggedUnion<{
89
+ readonly Ensure: Schema.TaggedStruct<"Ensure", {
90
+ computer: Schema.String;
91
+ }>;
92
+ readonly Exec: Schema.TaggedStruct<"Exec", {
93
+ readonly computer: Schema.String;
94
+ readonly command: Schema.String;
95
+ readonly key: Schema.String;
96
+ }>;
97
+ readonly Get: Schema.TaggedStruct<"Get", {
98
+ computer: Schema.String;
99
+ }>;
100
+ readonly List: Schema.TaggedStruct<"List", {}>;
101
+ readonly Pause: Schema.TaggedStruct<"Pause", {
102
+ computer: Schema.String;
103
+ hold: Schema.optionalKey<Schema.String>;
104
+ }>;
105
+ readonly Remove: Schema.TaggedStruct<"Remove", {
106
+ computer: Schema.String;
107
+ }>;
108
+ readonly Resume: Schema.TaggedStruct<"Resume", {
109
+ computer: Schema.String;
110
+ hold: Schema.optionalKey<Schema.String>;
111
+ }>;
112
+ readonly Step: Schema.TaggedStruct<"Step", {
113
+ readonly computer: Schema.String;
114
+ readonly command: Schema.String;
115
+ readonly name: Schema.String;
116
+ readonly timeoutSeconds: Schema.optionalKey<Schema.Int>;
117
+ }>;
118
+ readonly Stop: Schema.TaggedStruct<"Stop", {
119
+ computer: Schema.String;
120
+ }>;
121
+ }>>;
122
+ }, Schema.$Array<Schema.String>, Schema.Never, never>;
123
+ };
124
+ readonly key: Schema.String;
125
+ readonly queries: {
126
+ runs: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Record<Schema.String, Schema.$Array<Schema.String>>, Schema.Never, never>;
127
+ };
128
+ readonly state: Schema.Struct<{
129
+ readonly runs: Schema.$Record<Schema.String, Schema.$Array<Schema.String>>;
130
+ }>;
131
+ }>> & import("@akterjs/effect/contract").ActorHistory<import("@akterjs/effect/contract").DefinitionOf<{
132
+ readonly commands: {
133
+ run: import("@akterjs/effect/contract").Command<Schema.Struct<{
134
+ readonly ops: Schema.$Array<Schema.TaggedUnion<{
135
+ readonly Ensure: Schema.TaggedStruct<"Ensure", {
136
+ computer: Schema.String;
137
+ }>;
138
+ readonly Exec: Schema.TaggedStruct<"Exec", {
139
+ readonly computer: Schema.String;
140
+ readonly command: Schema.String;
141
+ readonly key: Schema.String;
142
+ }>;
143
+ readonly Get: Schema.TaggedStruct<"Get", {
144
+ computer: Schema.String;
145
+ }>;
146
+ readonly List: Schema.TaggedStruct<"List", {}>;
147
+ readonly Pause: Schema.TaggedStruct<"Pause", {
148
+ computer: Schema.String;
149
+ hold: Schema.optionalKey<Schema.String>;
150
+ }>;
151
+ readonly Remove: Schema.TaggedStruct<"Remove", {
152
+ computer: Schema.String;
153
+ }>;
154
+ readonly Resume: Schema.TaggedStruct<"Resume", {
155
+ computer: Schema.String;
156
+ hold: Schema.optionalKey<Schema.String>;
157
+ }>;
158
+ readonly Step: Schema.TaggedStruct<"Step", {
159
+ readonly computer: Schema.String;
160
+ readonly command: Schema.String;
161
+ readonly name: Schema.String;
162
+ readonly timeoutSeconds: Schema.optionalKey<Schema.Int>;
163
+ }>;
164
+ readonly Stop: Schema.TaggedStruct<"Stop", {
165
+ computer: Schema.String;
166
+ }>;
167
+ }>>;
168
+ }>, Schema.String, Schema.Never, never>;
169
+ };
170
+ readonly initial: () => {
171
+ runs: {};
172
+ };
173
+ readonly jobs: {
174
+ run: import("@akterjs/effect/contract").Job<{
175
+ readonly key: Schema.String;
176
+ readonly ops: Schema.$Array<Schema.TaggedUnion<{
177
+ readonly Ensure: Schema.TaggedStruct<"Ensure", {
178
+ computer: Schema.String;
179
+ }>;
180
+ readonly Exec: Schema.TaggedStruct<"Exec", {
181
+ readonly computer: Schema.String;
182
+ readonly command: Schema.String;
183
+ readonly key: Schema.String;
184
+ }>;
185
+ readonly Get: Schema.TaggedStruct<"Get", {
186
+ computer: Schema.String;
187
+ }>;
188
+ readonly List: Schema.TaggedStruct<"List", {}>;
189
+ readonly Pause: Schema.TaggedStruct<"Pause", {
190
+ computer: Schema.String;
191
+ hold: Schema.optionalKey<Schema.String>;
192
+ }>;
193
+ readonly Remove: Schema.TaggedStruct<"Remove", {
194
+ computer: Schema.String;
195
+ }>;
196
+ readonly Resume: Schema.TaggedStruct<"Resume", {
197
+ computer: Schema.String;
198
+ hold: Schema.optionalKey<Schema.String>;
199
+ }>;
200
+ readonly Step: Schema.TaggedStruct<"Step", {
201
+ readonly computer: Schema.String;
202
+ readonly command: Schema.String;
203
+ readonly name: Schema.String;
204
+ readonly timeoutSeconds: Schema.optionalKey<Schema.Int>;
205
+ }>;
206
+ readonly Stop: Schema.TaggedStruct<"Stop", {
207
+ computer: Schema.String;
208
+ }>;
209
+ }>>;
210
+ }, Schema.$Array<Schema.String>, Schema.Never, never>;
211
+ };
212
+ readonly key: Schema.String;
213
+ readonly queries: {
214
+ runs: import("@akterjs/effect/contract").Query<Schema.Void, Schema.$Record<Schema.String, Schema.$Array<Schema.String>>, Schema.Never, never>;
215
+ };
216
+ readonly state: Schema.Struct<{
217
+ readonly runs: Schema.$Record<Schema.String, Schema.$Array<Schema.String>>;
218
+ }>;
219
+ }>>;
220
+ export { ComputerOp, Workbench };
@@ -0,0 +1,54 @@
1
+ import { Schema } from "effect";
2
+ import { Akter } from "@akterjs/effect/contract";
3
+ // src/workbench/workbench.contract.ts
4
+ const Named = { computer: Schema.String };
5
+ const Held = {
6
+ ...Named,
7
+ hold: Schema.optionalKey(Schema.String)
8
+ };
9
+ /** One call a `run` job makes to the actor's computers, in order. */
10
+ const ComputerOp = Schema.TaggedUnion({
11
+ Ensure: Named,
12
+ Exec: {
13
+ ...Named,
14
+ command: Schema.String,
15
+ key: Schema.String
16
+ },
17
+ Get: Named,
18
+ List: {},
19
+ Pause: Held,
20
+ Remove: Named,
21
+ Resume: Held,
22
+ Step: {
23
+ ...Named,
24
+ command: Schema.String,
25
+ name: Schema.String,
26
+ timeoutSeconds: Schema.optionalKey(Schema.Int)
27
+ },
28
+ Stop: Named
29
+ });
30
+ /** Each finished `run` job's outcomes, one per call, by the job's id. */
31
+ const Runs = Schema.Record(Schema.String, Schema.Array(Schema.String));
32
+ /**
33
+ * The suite's actor for computer guarantees. `run` starts a job that makes its calls to the actor's computers, each
34
+ * in an activity or, for a `Step`, as a durable step of the job, and records what each answered; `runs` reads those
35
+ * records back.
36
+ */
37
+ const Workbench = Akter.make("Workbench", {
38
+ commands: { run: Akter.command({
39
+ payload: { ops: Schema.Array(ComputerOp) },
40
+ success: Schema.String
41
+ }) },
42
+ initial: () => ({ runs: {} }),
43
+ jobs: { run: Akter.job({
44
+ payload: {
45
+ key: Schema.String,
46
+ ops: Schema.Array(ComputerOp)
47
+ },
48
+ success: Schema.Array(Schema.String)
49
+ }) },
50
+ key: Schema.String,
51
+ queries: { runs: Akter.query({ success: Runs }) },
52
+ state: Schema.Struct({ runs: Runs })
53
+ });
54
+ export { ComputerOp, Workbench };
package/package.json CHANGED
@@ -1,6 +1,89 @@
1
1
  {
2
2
  "name": "@akterjs/conformance",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The test suite every Akter engine must pass: the local engine here and the production engine on Akter Cloud.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Rika-Labs/akter.git",
9
+ "directory": "packages/conformance"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "imports": {
18
+ "#src/*": "./src/*"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/engine-conformance.suite.d.mts",
23
+ "default": "./dist/engine-conformance.suite.mjs"
24
+ },
25
+ "./client": {
26
+ "types": "./dist/client-contract.suite.d.mts",
27
+ "default": "./dist/client-contract.suite.mjs"
28
+ },
29
+ "./computers": {
30
+ "types": "./dist/workbench/computer-conformance.suite.d.mts",
31
+ "default": "./dist/workbench/computer-conformance.suite.mjs"
32
+ },
33
+ "./fork": {
34
+ "types": "./dist/fork/fork-conformance.suite.d.mts",
35
+ "default": "./dist/fork/fork-conformance.suite.mjs"
36
+ },
37
+ "./plain": {
38
+ "types": "./dist/ledger/plain-ledger.actor.d.mts",
39
+ "default": "./dist/ledger/plain-ledger.actor.mjs"
40
+ },
41
+ "./tally": {
42
+ "types": "./dist/tally.actor.d.mts",
43
+ "default": "./dist/tally.actor.mjs"
44
+ }
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true
49
+ },
50
+ "devDependencies": {
51
+ "@akterjs/client": "0.1.0",
52
+ "@akterjs/effect": "0.1.0",
53
+ "@akterjs/sdk": "0.1.0",
54
+ "@effect/platform-node": "4.1.2",
55
+ "@effect/vitest": "4.1.2",
56
+ "effect": "4.1.2",
57
+ "publint": "0.3.25",
58
+ "tsdown": "0.23.5",
59
+ "vitest": "5.1.0",
60
+ "zod": "4.6.7"
61
+ },
62
+ "peerDependencies": {
63
+ "@akterjs/client": "0.1.0",
64
+ "@akterjs/effect": "0.1.0",
65
+ "@akterjs/sdk": "0.1.0",
66
+ "@effect/vitest": "4.1.2",
67
+ "effect": "4.1.2",
68
+ "vitest": "5.1.0",
69
+ "zod": "4.6.7"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@akterjs/client": {
73
+ "optional": true
74
+ },
75
+ "@akterjs/sdk": {
76
+ "optional": true
77
+ },
78
+ "zod": {
79
+ "optional": true
80
+ }
81
+ },
82
+ "scripts": {
83
+ "typecheck": "effect-tsgo tsrs --noEmit -p tsconfig.json",
84
+ "lint": "effect-tsgo --tsrs-lint oxlint -c ../../oxlint.config.ts --type-aware --tsconfig tsconfig.json src tests",
85
+ "test": "vitest run",
86
+ "test:bun": "bunx --bun vitest run",
87
+ "build": "tsdown"
88
+ }
6
89
  }