@llm4ts/core 2.13.0 → 2.14.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.
@@ -0,0 +1,84 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { Board, DuplicateFact, type Fact } from "./Fact.ts";
4
+ import type { Ruleset } from "./Ruleset.ts";
5
+ declare const Firing_base: Schema.Class<Firing, Schema.Struct<{
6
+ readonly rule: Schema.String;
7
+ readonly kind: Schema.Literals<readonly ["derive", "judge", "rule"]>;
8
+ readonly readKeys: Schema.$Array<Schema.String>;
9
+ readonly postedKeys: Schema.$Array<Schema.String>;
10
+ /** Epoch milliseconds. */
11
+ readonly startedAt: Schema.Number;
12
+ /** Milliseconds. */
13
+ readonly duration: Schema.Number;
14
+ /** `failed`: the consequence failed and `postedKeys` are the rule's defaults. */
15
+ readonly outcome: Schema.Literals<readonly ["posted", "failed"]>;
16
+ readonly judgment: Schema.optionalKey<Schema.Struct<{
17
+ readonly backend: Schema.String;
18
+ readonly identity: Schema.String;
19
+ }>>;
20
+ }>, {}>;
21
+ export declare class Firing extends Firing_base {
22
+ }
23
+ declare const RuleFailure_base: Schema.Class<RuleFailure, Schema.Struct<{
24
+ readonly rule: Schema.String;
25
+ readonly tag: Schema.String;
26
+ readonly message: Schema.String;
27
+ readonly error: Schema.Defect;
28
+ }>, {}>;
29
+ /**
30
+ * A rule's typed failure. `error` is the value itself while the result is in
31
+ * memory; `tag` and `message` are what survive encoding, since `Defect`
32
+ * encodes any error as a plain one.
33
+ */
34
+ export declare class RuleFailure extends RuleFailure_base {
35
+ }
36
+ declare const RunResult_base: Schema.Class<RunResult, Schema.Struct<{
37
+ readonly board: typeof Board;
38
+ readonly trace: Schema.$Array<typeof Firing>;
39
+ readonly failures: Schema.$Array<typeof RuleFailure>;
40
+ }>, {}>;
41
+ export declare class RunResult extends RunResult_base {
42
+ }
43
+ declare const UndeclaredFact_base: Schema.Class<UndeclaredFact, Schema.TaggedStruct<"UndeclaredFact", {
44
+ readonly rule: Schema.String;
45
+ readonly key: Schema.String;
46
+ }>, import("effect/Cause").YieldableError>;
47
+ export declare class UndeclaredFact extends UndeclaredFact_base {
48
+ get message(): string;
49
+ }
50
+ declare const MissingImport_base: Schema.Class<MissingImport, Schema.TaggedStruct<"MissingImport", {
51
+ readonly keys: Schema.$Array<Schema.String>;
52
+ }>, import("effect/Cause").YieldableError>;
53
+ export declare class MissingImport extends MissingImport_base {
54
+ get message(): string;
55
+ }
56
+ declare const WaitingRule_base: Schema.Class<WaitingRule, Schema.Struct<{
57
+ readonly rule: Schema.String;
58
+ readonly missingKeys: Schema.$Array<Schema.String>;
59
+ }>, {}>;
60
+ export declare class WaitingRule extends WaitingRule_base {
61
+ }
62
+ declare const MissingExport_base: Schema.Class<MissingExport, Schema.Struct<{
63
+ readonly key: Schema.String;
64
+ readonly waitingRules: Schema.$Array<typeof WaitingRule>;
65
+ /** Producers that fired, failed or were vetoed by `when` without posting the key. */
66
+ readonly silentRules: Schema.$Array<Schema.String>;
67
+ }>, {}>;
68
+ /** Why an export is missing: who still waits, and who ran and did not post it. */
69
+ export declare class MissingExport extends MissingExport_base {
70
+ }
71
+ declare const ExportsMissing_base: Schema.Class<ExportsMissing, Schema.TaggedStruct<"ExportsMissing", {
72
+ readonly missing: Schema.$Array<typeof MissingExport>;
73
+ /** What did run, so a stall can be explained. */
74
+ readonly trace: Schema.$Array<typeof Firing>;
75
+ readonly failures: Schema.$Array<typeof RuleFailure>;
76
+ }>, import("effect/Cause").YieldableError>;
77
+ export declare class ExportsMissing extends ExportsMissing_base {
78
+ get message(): string;
79
+ }
80
+ export declare const RunError: Schema.Union<readonly [typeof DuplicateFact, typeof UndeclaredFact, typeof MissingImport, typeof ExportsMissing]>;
81
+ export type RunError = typeof RunError.Type;
82
+ export declare const runRuleset: <E, R>(ruleset: Ruleset<E, R>, initial: ReadonlyArray<Fact>) => Effect.Effect<RunResult, RunError, R>;
83
+ export {};
84
+ //# sourceMappingURL=Run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Run.d.ts","sourceRoot":"","sources":["../../src/blackboard/Run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAEtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;;;;;;IAiBzC,0BAA0B;;IAE1B,oBAAoB;;IAEpB,iFAAiF;;;;;;;AATnF,qBAAa,MAAO,SAAQ,WAY1B;CAAG;;;;;;;AAEL;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,gBAK/B;CAAG;;;;;;AAaL,qBAAa,SAAU,SAAQ,cAI7B;CAAG;;;;;AAEL,qBAAa,cAAe,SAAQ,mBAGlC;IACA,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF;;;;AAED,qBAAa,aAAc,SAAQ,kBAEjC;IACA,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF;;;;;AAED,qBAAa,WAAY,SAAQ,gBAG/B;CAAG;;;;IAMH,qFAAqF;;;AAJvF,kFAAkF;AAClF,qBAAa,aAAc,SAAQ,kBAKjC;CAAG;;;IAIH,iDAAiD;;;;AAFnD,qBAAa,cAAe,SAAQ,mBAKlC;IACA,IAAI,OAAO,IAAI,MAAM,CAapB;CACF;AAED,eAAO,MAAM,QAAQ,mHAA+E,CAAA;AACpG,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAmD3C,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAC7B,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,SAAS,aAAa,CAAC,IAAI,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,CAiFnC,CAAA"}
@@ -0,0 +1,195 @@
1
+ import * as Clock from "effect/Clock";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import * as Result from "effect/Result";
5
+ import * as Schema from "effect/Schema";
6
+ import { Board, DuplicateFact, writeOnce } from "./Fact.js";
7
+ /**
8
+ * Forward chaining in rounds (ADR 0020). A round fires, concurrently, every
9
+ * rule whose condition is satisfied by the current snapshot; the facts they
10
+ * post are then applied one by one, write-once. The run ends when a round
11
+ * fires nothing, and it succeeds only if every export is on the board. A
12
+ * rule's failure is recorded and its defaults posted; a defect fails the run.
13
+ */
14
+ const JudgmentNote = Schema.Struct({ backend: Schema.String, identity: Schema.String });
15
+ export class Firing extends Schema.Class("Firing")({
16
+ rule: Schema.String,
17
+ kind: Schema.Literals(["derive", "judge", "rule"]),
18
+ readKeys: Schema.Array(Schema.String),
19
+ postedKeys: Schema.Array(Schema.String),
20
+ /** Epoch milliseconds. */
21
+ startedAt: Schema.Number,
22
+ /** Milliseconds. */
23
+ duration: Schema.Number,
24
+ /** `failed`: the consequence failed and `postedKeys` are the rule's defaults. */
25
+ outcome: Schema.Literals(["posted", "failed"]),
26
+ judgment: Schema.optionalKey(JudgmentNote)
27
+ }) {
28
+ }
29
+ /**
30
+ * A rule's typed failure. `error` is the value itself while the result is in
31
+ * memory; `tag` and `message` are what survive encoding, since `Defect`
32
+ * encodes any error as a plain one.
33
+ */
34
+ export class RuleFailure extends Schema.Class("RuleFailure")({
35
+ rule: Schema.String,
36
+ tag: Schema.String,
37
+ message: Schema.String,
38
+ error: Schema.Defect()
39
+ }) {
40
+ }
41
+ const isTagged = (error) => typeof error === "object" && error !== null && "_tag" in error && typeof error._tag === "string";
42
+ const ruleFailure = (rule, error) => RuleFailure.make({
43
+ rule,
44
+ tag: isTagged(error) ? error._tag : error instanceof Error ? error.name : typeof error,
45
+ message: error instanceof Error ? error.message : String(error),
46
+ error
47
+ });
48
+ export class RunResult extends Schema.Class("RunResult")({
49
+ board: Board,
50
+ trace: Schema.Array(Firing),
51
+ failures: Schema.Array(RuleFailure)
52
+ }) {
53
+ }
54
+ export class UndeclaredFact extends Schema.TaggedError()("UndeclaredFact", {
55
+ rule: Schema.String,
56
+ key: Schema.String
57
+ }) {
58
+ get message() {
59
+ return `rule ${this.rule} posted "${this.key}", which it does not declare`;
60
+ }
61
+ }
62
+ export class MissingImport extends Schema.TaggedError()("MissingImport", {
63
+ keys: Schema.Array(Schema.String)
64
+ }) {
65
+ get message() {
66
+ return `initial facts miss the imports: ${this.keys.join(", ")}`;
67
+ }
68
+ }
69
+ export class WaitingRule extends Schema.Class("WaitingRule")({
70
+ rule: Schema.String,
71
+ missingKeys: Schema.Array(Schema.String)
72
+ }) {
73
+ }
74
+ /** Why an export is missing: who still waits, and who ran and did not post it. */
75
+ export class MissingExport extends Schema.Class("MissingExport")({
76
+ key: Schema.String,
77
+ waitingRules: Schema.Array(WaitingRule),
78
+ /** Producers that fired, failed or were vetoed by `when` without posting the key. */
79
+ silentRules: Schema.Array(Schema.String)
80
+ }) {
81
+ }
82
+ export class ExportsMissing extends Schema.TaggedError()("ExportsMissing", {
83
+ missing: Schema.Array(MissingExport),
84
+ /** What did run, so a stall can be explained. */
85
+ trace: Schema.Array(Firing),
86
+ failures: Schema.Array(RuleFailure)
87
+ }) {
88
+ get message() {
89
+ const reasons = this.missing.map((entry) => {
90
+ const waiting = entry.waitingRules.map((rule) => `${rule.rule} waits for ${rule.missingKeys.join(", ")}`);
91
+ const silent = entry.silentRules.map((rule) => `${rule} fired without posting it`);
92
+ return `"${entry.key}" (${[...waiting, ...silent].join("; ")})`;
93
+ });
94
+ const failed = this.failures.length === 0
95
+ ? ""
96
+ : `; failed: ${this.failures.map((f) => `${f.rule} (${f.tag})`).join(", ")}`;
97
+ return `the run ended without: ${reasons.join(", ")}${failed}`;
98
+ }
99
+ }
100
+ export const RunError = Schema.Union([DuplicateFact, UndeclaredFact, MissingImport, ExportsMissing]);
101
+ /** Apply posted facts write-once; the keys actually written, in first-seen order. */
102
+ const post = (facts, rule, allowed, posted) => Effect.gen(function* () {
103
+ let current = facts;
104
+ const keys = [];
105
+ for (const fact of posted) {
106
+ if (!allowed.includes(fact.key)) {
107
+ return yield* Effect.fail(UndeclaredFact.make({ rule, key: fact.key }));
108
+ }
109
+ const written = writeOnce(current, fact.key, yield* fact.encoded);
110
+ if (Result.isFailure(written))
111
+ return yield* Effect.fail(written.failure);
112
+ current = written.success;
113
+ if (!keys.includes(fact.key))
114
+ keys.push(fact.key);
115
+ }
116
+ return [current, keys];
117
+ });
118
+ const fireAll = (ready) => Effect.forEach(ready, ({ rule, firing }) => Effect.gen(function* () {
119
+ const startedAt = yield* Clock.currentTimeMillis;
120
+ const result = yield* Effect.result(firing);
121
+ const finishedAt = yield* Clock.currentTimeMillis;
122
+ return { rule, startedAt, duration: finishedAt - startedAt, result };
123
+ }), { concurrency: "unbounded" });
124
+ export const runRuleset = (ruleset, initial) => Effect.gen(function* () {
125
+ const initialKeys = initial.map((fact) => fact.key);
126
+ const missingImports = ruleset.imports.filter((key) => !initialKeys.includes(key));
127
+ if (missingImports.length > 0) {
128
+ return yield* Effect.fail(MissingImport.make({ keys: missingImports }));
129
+ }
130
+ let [facts] = yield* post(new Map(), "(initial)", ruleset.imports, initial);
131
+ const pending = new Map(ruleset.rules.map((rule) => [rule.name, rule]));
132
+ const trace = [];
133
+ const failures = [];
134
+ for (;;) {
135
+ const ready = [];
136
+ for (const rule of pending.values()) {
137
+ if (!rule.reads.every((key) => facts.has(key)))
138
+ continue;
139
+ const prepared = yield* rule.prepare(facts);
140
+ // Every read is present and facts never change, so a None now is a No forever.
141
+ pending.delete(rule.name);
142
+ if (Option.isSome(prepared))
143
+ ready.push({ rule, firing: prepared.value });
144
+ }
145
+ if (ready.length === 0)
146
+ break;
147
+ const outcomes = yield* fireAll(ready);
148
+ for (const outcome of outcomes) {
149
+ const failed = Result.isFailure(outcome.result);
150
+ if (Result.isFailure(outcome.result)) {
151
+ failures.push(ruleFailure(outcome.rule.name, outcome.result.failure));
152
+ }
153
+ const posted = Result.isSuccess(outcome.result)
154
+ ? outcome.result.success
155
+ : { facts: outcome.rule.defaults };
156
+ const [next, postedKeys] = yield* post(facts, outcome.rule.name, outcome.rule.produces, posted.facts);
157
+ facts = next;
158
+ trace.push(Firing.make({
159
+ rule: outcome.rule.name,
160
+ kind: outcome.rule.kind,
161
+ readKeys: outcome.rule.reads,
162
+ postedKeys,
163
+ startedAt: outcome.startedAt,
164
+ duration: outcome.duration,
165
+ outcome: failed ? "failed" : "posted",
166
+ ...(posted.judgment === undefined ? {} : { judgment: posted.judgment })
167
+ }));
168
+ }
169
+ }
170
+ const missing = ruleset.exports.filter((key) => !facts.has(key));
171
+ if (missing.length > 0) {
172
+ return yield* Effect.fail(ExportsMissing.make({
173
+ missing: missing.map((key) => MissingExport.make({
174
+ key,
175
+ waitingRules: [...pending.values()]
176
+ .filter((rule) => rule.produces.includes(key))
177
+ .map((rule) => WaitingRule.make({
178
+ rule: rule.name,
179
+ missingKeys: rule.reads.filter((read) => !facts.has(read))
180
+ })),
181
+ silentRules: ruleset.rules
182
+ .filter((rule) => rule.produces.includes(key) && !pending.has(rule.name))
183
+ .map((rule) => rule.name)
184
+ })),
185
+ trace,
186
+ failures
187
+ }));
188
+ }
189
+ return RunResult.make({
190
+ board: Board.make({ facts: Object.fromEntries(facts) }),
191
+ trace,
192
+ failures
193
+ });
194
+ });
195
+ //# sourceMappingURL=Run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Run.js","sourceRoot":"","sources":["../../src/blackboard/Run.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/core",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Effect-native LLM contracts, providers, tools, evaluation, and observability",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,10 @@
14
14
  ],
15
15
  "exports": {
16
16
  "./AgentSession": "./dist/AgentSession.js",
17
+ "./blackboard/Fact": "./dist/blackboard/Fact.js",
18
+ "./blackboard/Rule": "./dist/blackboard/Rule.js",
19
+ "./blackboard/Ruleset": "./dist/blackboard/Ruleset.js",
20
+ "./blackboard/Run": "./dist/blackboard/Run.js",
17
21
  "./Capability": "./dist/Capability.js",
18
22
  "./Connector": "./dist/Connector.js",
19
23
  "./ConnectorConfig": "./dist/ConnectorConfig.js",
@@ -0,0 +1,104 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Result from "effect/Result"
3
+ import * as Schema from "effect/Schema"
4
+
5
+ /**
6
+ * Facts on a blackboard (ADR 0020). A key names a fact and carries its
7
+ * schema; the board holds facts in their encoded JSON form, so it persists
8
+ * as-is and a read decodes through the key's schema instead of asserting a
9
+ * type. A key is written once per run: an equal rewrite is a no-op, a
10
+ * different one is a `DuplicateFact`.
11
+ */
12
+
13
+ export interface Fact {
14
+ readonly key: string
15
+ /** The value in the key's encoded form; a value typed `A` cannot fail to encode. */
16
+ readonly encoded: Effect.Effect<Schema.Json>
17
+ }
18
+
19
+ export interface FactKey<A> {
20
+ readonly name: string
21
+ of(value: A): Fact
22
+ /** Decode a stored value; the board only holds what a key encoded, so a failure is a defect. */
23
+ readonly read: (encoded: unknown) => Effect.Effect<A>
24
+ }
25
+
26
+ export const makeKey = <A, I>(name: string, schema: Schema.Codec<A, I>): FactKey<A> => {
27
+ const encode = Schema.encodeEffect(schema)
28
+ const decode = Schema.decodeUnknownEffect(schema)
29
+ const json = Schema.decodeUnknownEffect(Schema.Json)
30
+ return {
31
+ name,
32
+ of: (value) => ({
33
+ key: name,
34
+ encoded: encode(value).pipe(Effect.flatMap(json), Effect.orDie)
35
+ }),
36
+ read: (encoded) => decode(encoded).pipe(Effect.orDie)
37
+ }
38
+ }
39
+
40
+ export class MissingFact extends Schema.TaggedError<MissingFact>()("MissingFact", {
41
+ key: Schema.String
42
+ }) {
43
+ get message(): string {
44
+ return `fact "${this.key}" is not on the board`
45
+ }
46
+ }
47
+
48
+ export class DuplicateFact extends Schema.TaggedError<DuplicateFact>()("DuplicateFact", {
49
+ key: Schema.String
50
+ }) {
51
+ get message(): string {
52
+ return `fact "${this.key}" was posted twice with different values`
53
+ }
54
+ }
55
+
56
+ export class Board extends Schema.Class<Board>("Board")({
57
+ facts: Schema.Record(Schema.String, Schema.Json)
58
+ }) {
59
+ static readonly empty: Board = Board.make({ facts: {} })
60
+
61
+ has(name: string): boolean {
62
+ return Object.hasOwn(this.facts, name)
63
+ }
64
+
65
+ get<A>(key: FactKey<A>): Effect.Effect<A, MissingFact> {
66
+ return this.has(key.name)
67
+ ? key.read(this.facts[key.name])
68
+ : Effect.fail(MissingFact.make({ key: key.name }))
69
+ }
70
+ }
71
+
72
+ const isObject = (value: Schema.Json): value is { readonly [key: string]: Schema.Json } =>
73
+ typeof value === "object" && value !== null && !Array.isArray(value)
74
+
75
+ /** Structural JSON equality: arrays ordered, object keys not. */
76
+ export const sameJson = (a: Schema.Json, b: Schema.Json): boolean => {
77
+ if (a === b) return true
78
+ if (Array.isArray(a) && Array.isArray(b)) {
79
+ return a.length === b.length && a.every((item, index) => sameJson(item, b[index] ?? null))
80
+ }
81
+ if (isObject(a) && isObject(b)) {
82
+ const keysA = Object.keys(a)
83
+ const keysB = Object.keys(b)
84
+ return (
85
+ keysA.length === keysB.length &&
86
+ keysA.every((key) => Object.hasOwn(b, key) && sameJson(a[key] ?? null, b[key] ?? null))
87
+ )
88
+ }
89
+ return false
90
+ }
91
+
92
+ /** The board after a write: unchanged for an equal rewrite, extended for a new key. */
93
+ export const writeOnce = (
94
+ facts: ReadonlyMap<string, Schema.Json>,
95
+ key: string,
96
+ value: Schema.Json
97
+ ): Result.Result<ReadonlyMap<string, Schema.Json>, DuplicateFact> => {
98
+ if (!facts.has(key)) {
99
+ return Result.succeed(new Map(facts).set(key, value))
100
+ }
101
+ return sameJson(facts.get(key) ?? null, value)
102
+ ? Result.succeed(facts)
103
+ : Result.fail(DuplicateFact.make({ key }))
104
+ }
@@ -0,0 +1,201 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Option from "effect/Option"
3
+ import type * as Schema from "effect/Schema"
4
+ import { Judgment, type JudgmentBackendError, type JudgmentInput } from "../judgment/Judgment.ts"
5
+ import type { JudgmentResult } from "../judgment/Schemas.ts"
6
+ import type { Fact, FactKey } from "./Fact.ts"
7
+
8
+ /**
9
+ * Rules of a blackboard (ADR 0020). A condition names the keys it reads
10
+ * (what validation and pruning reason on) and evaluates to the typed value
11
+ * a consequence receives; a rule pairs a condition with a consequence that
12
+ * posts facts. Facts are write-once, so a rule fires at most once per run.
13
+ */
14
+
15
+ /** A snapshot of the board: encoded facts by key. */
16
+ export type Facts = ReadonlyMap<string, Schema.Json>
17
+
18
+ export interface Condition<A> {
19
+ readonly keys: ReadonlyArray<string>
20
+ /** None while a key is missing or a `when` predicate rejects the value. */
21
+ readonly evaluate: (facts: Facts) => Effect.Effect<Option.Option<A>>
22
+ }
23
+
24
+ export const on = <A>(key: FactKey<A>): Condition<A> => ({
25
+ keys: [key.name],
26
+ evaluate: (facts) =>
27
+ facts.has(key.name)
28
+ ? Effect.map(key.read(facts.get(key.name)), Option.some)
29
+ : Effect.succeed(Option.none())
30
+ })
31
+
32
+ /**
33
+ * Satisfied once every key is present; the value is the tuple of decoded
34
+ * values, in order. Typed by overload up to eight keys: one implementation
35
+ * over a loose tuple, so no assertion is needed to give each call its shape.
36
+ */
37
+ export function all<A>(k1: FactKey<A>): Condition<readonly [A]>
38
+ export function all<A, B>(k1: FactKey<A>, k2: FactKey<B>): Condition<readonly [A, B]>
39
+ export function all<A, B, C>(
40
+ k1: FactKey<A>,
41
+ k2: FactKey<B>,
42
+ k3: FactKey<C>
43
+ ): Condition<readonly [A, B, C]>
44
+ export function all<A, B, C, D>(
45
+ k1: FactKey<A>,
46
+ k2: FactKey<B>,
47
+ k3: FactKey<C>,
48
+ k4: FactKey<D>
49
+ ): Condition<readonly [A, B, C, D]>
50
+ export function all<A, B, C, D, E>(
51
+ k1: FactKey<A>,
52
+ k2: FactKey<B>,
53
+ k3: FactKey<C>,
54
+ k4: FactKey<D>,
55
+ k5: FactKey<E>
56
+ ): Condition<readonly [A, B, C, D, E]>
57
+ export function all<A, B, C, D, E, F>(
58
+ k1: FactKey<A>,
59
+ k2: FactKey<B>,
60
+ k3: FactKey<C>,
61
+ k4: FactKey<D>,
62
+ k5: FactKey<E>,
63
+ k6: FactKey<F>
64
+ ): Condition<readonly [A, B, C, D, E, F]>
65
+ export function all<A, B, C, D, E, F, G>(
66
+ k1: FactKey<A>,
67
+ k2: FactKey<B>,
68
+ k3: FactKey<C>,
69
+ k4: FactKey<D>,
70
+ k5: FactKey<E>,
71
+ k6: FactKey<F>,
72
+ k7: FactKey<G>
73
+ ): Condition<readonly [A, B, C, D, E, F, G]>
74
+ export function all<A, B, C, D, E, F, G, H>(
75
+ k1: FactKey<A>,
76
+ k2: FactKey<B>,
77
+ k3: FactKey<C>,
78
+ k4: FactKey<D>,
79
+ k5: FactKey<E>,
80
+ k6: FactKey<F>,
81
+ k7: FactKey<G>,
82
+ k8: FactKey<H>
83
+ ): Condition<readonly [A, B, C, D, E, F, G, H]>
84
+ export function all(...keys: ReadonlyArray<FactKey<unknown>>): Condition<ReadonlyArray<unknown>> {
85
+ return {
86
+ keys: keys.map((key) => key.name),
87
+ evaluate: (facts) =>
88
+ keys.every((key) => facts.has(key.name))
89
+ ? Effect.map(
90
+ Effect.forEach(keys, (key) => key.read(facts.get(key.name))),
91
+ (values) => Option.some(values)
92
+ )
93
+ : Effect.succeed(Option.none())
94
+ }
95
+ }
96
+
97
+ export const when = <A>(
98
+ condition: Condition<A>,
99
+ predicate: (value: A) => boolean
100
+ ): Condition<A> => ({
101
+ keys: condition.keys,
102
+ evaluate: (facts) =>
103
+ Effect.map(condition.evaluate(facts), (value) =>
104
+ Option.isSome(value) && predicate(value.value) ? value : Option.none()
105
+ )
106
+ })
107
+
108
+ export interface JudgmentNote {
109
+ readonly backend: string
110
+ readonly identity: string
111
+ }
112
+
113
+ export interface Posted {
114
+ readonly facts: ReadonlyArray<Fact>
115
+ /** Set by `judge`: which backend and checkpoint answered, for the trace. */
116
+ readonly judgment?: JudgmentNote
117
+ }
118
+
119
+ export type RuleKind = "derive" | "judge" | "rule"
120
+
121
+ export interface Rule<E = never, R = never> {
122
+ readonly name: string
123
+ readonly kind: RuleKind
124
+ readonly reads: ReadonlyArray<string>
125
+ /** The keys this rule may post; posting another is an `UndeclaredFact`. */
126
+ readonly produces: ReadonlyArray<string>
127
+ /** Posted instead when the consequence fails. */
128
+ readonly defaults: ReadonlyArray<Fact>
129
+ /** Some(firing) when the condition is satisfied against the snapshot. */
130
+ readonly prepare: (facts: Facts) => Effect.Effect<Option.Option<Effect.Effect<Posted, E, R>>>
131
+ }
132
+
133
+ export interface RuleOptions<A, E, R> {
134
+ readonly name: string
135
+ readonly condition: Condition<A>
136
+ readonly produces: ReadonlyArray<FactKey<unknown>>
137
+ readonly consequence: (value: A) => Effect.Effect<ReadonlyArray<Fact>, E, R>
138
+ readonly defaults?: ReadonlyArray<Fact>
139
+ readonly kind?: RuleKind
140
+ }
141
+
142
+ export const rule = <A, E, R>(options: RuleOptions<A, E, R>): Rule<E, R> =>
143
+ makeRule(options, (value) => Effect.map(options.consequence(value), (facts) => ({ facts })))
144
+
145
+ /** The general constructor other constructors build on: the firing may annotate the trace. */
146
+ export const makeRule = <A, E, R>(
147
+ options: Omit<RuleOptions<A, E, R>, "consequence">,
148
+ fire: (value: A) => Effect.Effect<Posted, E, R>
149
+ ): Rule<E, R> => ({
150
+ name: options.name,
151
+ kind: options.kind ?? "rule",
152
+ reads: options.condition.keys,
153
+ produces: options.produces.map((key) => key.name),
154
+ defaults: options.defaults ?? [],
155
+ prepare: (facts) =>
156
+ Effect.map(options.condition.evaluate(facts), (value) =>
157
+ Option.isSome(value) ? Option.some(fire(value.value)) : Option.none()
158
+ )
159
+ })
160
+
161
+ export interface DeriveOptions<A> {
162
+ readonly name: string
163
+ readonly condition: Condition<A>
164
+ readonly produces: ReadonlyArray<FactKey<unknown>>
165
+ readonly derive: (value: A) => ReadonlyArray<Fact>
166
+ }
167
+
168
+ /** A pure, deterministic rule. */
169
+ export const derive = <A>(options: DeriveOptions<A>): Rule =>
170
+ makeRule({ ...options, kind: "derive" }, (value) =>
171
+ Effect.succeed({ facts: options.derive(value) })
172
+ )
173
+
174
+ export interface JudgeOptions<A> {
175
+ readonly name: string
176
+ readonly condition: Condition<A>
177
+ readonly produces: ReadonlyArray<FactKey<unknown>>
178
+ /** The State and Questions for this firing; `state` is data, never instructions. */
179
+ readonly ask: (value: A) => JudgmentInput
180
+ /**
181
+ * Facts from the typed result. Post the `Answer` itself (probability,
182
+ * support, origin), not a boolean read off it: deciding is another rule's
183
+ * job. Unanswered questions arrive in `result.failures`.
184
+ */
185
+ readonly post: (result: JudgmentResult, value: A) => ReadonlyArray<Fact>
186
+ /** Posted when the backend itself is unreachable or rejects the request. */
187
+ readonly defaults?: ReadonlyArray<Fact>
188
+ }
189
+
190
+ /** A rule that asks the Judgment service; its firing notes the backend and checkpoint for the trace. */
191
+ export const judge = <A>(options: JudgeOptions<A>): Rule<JudgmentBackendError, Judgment> =>
192
+ makeRule({ ...options, kind: "judge" }, (value) =>
193
+ Effect.gen(function* () {
194
+ const judgment = yield* Judgment
195
+ const result = yield* judgment.judge(options.ask(value))
196
+ return {
197
+ facts: options.post(result, value),
198
+ judgment: { backend: judgment.backend, identity: judgment.identity }
199
+ }
200
+ })
201
+ )