factbag 0.0.0-alpha.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/CHANGELOG.md +5 -0
- package/LICENSE +202 -0
- package/README.md +44 -0
- package/dist/core/chunk-4IP7CXNV.js +86 -0
- package/dist/core/chunk-UVOCOH2I.js +41 -0
- package/dist/core/chunk-X3LSDJL4.js +59 -0
- package/dist/core/chunk-YF4PP42K.js +8 -0
- package/dist/core/dsl/enumeration.d.ts +3 -0
- package/dist/core/dsl/formula.d.ts +62 -0
- package/dist/core/dsl/index.d.ts +21 -0
- package/dist/core/dsl/index.js +132 -0
- package/dist/core/dsl/operators/aggregate.d.ts +30 -0
- package/dist/core/dsl/operators/arithmetic.d.ts +27 -0
- package/dist/core/dsl/operators/comparison.d.ts +13 -0
- package/dist/core/dsl/predicate.d.ts +33 -0
- package/dist/core/dsl/types.d.ts +59 -0
- package/dist/core/dsl/variable.d.ts +3 -0
- package/dist/core/exceptions/programmer-errors.d.ts +36 -0
- package/dist/core/explanation/explain.d.ts +20 -0
- package/dist/core/explanation/index.d.ts +8 -0
- package/dist/core/explanation/index.js +187 -0
- package/dist/core/explanation/types.d.ts +172 -0
- package/dist/core/knowledge-base/assertions/exclusive-definitions.d.ts +3 -0
- package/dist/core/knowledge-base/index.d.ts +13 -0
- package/dist/core/knowledge-base/index.js +196 -0
- package/dist/core/knowledge-base/knowledge-base.d.ts +81 -0
- package/dist/core/query/index.d.ts +10 -0
- package/dist/core/query/index.js +1947 -0
- package/dist/core/query/query.d.ts +47 -0
- package/dist/core/solver/candidate-search.d.ts +12 -0
- package/dist/core/solver/goal-value.d.ts +6 -0
- package/dist/core/solver/matching.d.ts +10 -0
- package/dist/core/solver/query-validation/constructive-recursion.d.ts +3 -0
- package/dist/core/solver/query-validation/constructive-variable-use.d.ts +3 -0
- package/dist/core/solver/query-validation/context.d.ts +10 -0
- package/dist/core/solver/query-validation/index.d.ts +1 -0
- package/dist/core/solver/query-validation/utils.d.ts +27 -0
- package/dist/core/solver/query-validation/validate-query.d.ts +6 -0
- package/dist/core/solver/solve-all.d.ts +6 -0
- package/dist/core/solver/solve.d.ts +6 -0
- package/dist/core/types/annotations.d.ts +6 -0
- package/dist/core/types/atom.d.ts +55 -0
- package/dist/core/types/common.d.ts +7 -0
- package/dist/core/types/evidence-metadata.d.ts +18 -0
- package/dist/core/types/goal.d.ts +103 -0
- package/dist/core/types/judgment.d.ts +137 -0
- package/dist/core/types/literal.d.ts +25 -0
- package/dist/core/types/predicate.d.ts +35 -0
- package/dist/core/types/rule.d.ts +16 -0
- package/dist/core/types/schema.d.ts +33 -0
- package/dist/core/types/variable.d.ts +13 -0
- package/dist/core/utils/assert.d.ts +4 -0
- package/dist/core/utils/normalize-to-list.d.ts +2 -0
- package/dist/corpus/chunkers.d.ts +7 -0
- package/dist/corpus/corpus.d.ts +41 -0
- package/dist/corpus/index.d.ts +8 -0
- package/dist/corpus/index.js +312 -0
- package/dist/corpus/records.d.ts +8 -0
- package/dist/corpus/text.d.ts +6 -0
- package/dist/corpus/types.d.ts +62 -0
- package/dist/guards/guarded-kb.d.ts +59 -0
- package/dist/guards/index.d.ts +7 -0
- package/dist/guards/index.js +121 -0
- package/dist/guards/types.d.ts +15 -0
- package/dist/jev/chunk-2UXHML67.js +206 -0
- package/dist/jev/chunk-M7RHUI2O.js +15 -0
- package/dist/jev/classifier.d.ts +28 -0
- package/dist/jev/classifier.js +171 -0
- package/dist/jev/client.d.ts +62 -0
- package/dist/jev/errors.d.ts +22 -0
- package/dist/jev/errors.js +6 -0
- package/dist/jev/jobs.d.ts +61 -0
- package/dist/jev/resolve.d.ts +82 -0
- package/dist/jev/resolve.js +99 -0
- package/dist/jev/tools.d.ts +9 -0
- package/dist/jev/types.d.ts +63 -0
- package/package.json +92 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AggregateBuilder, NumericTerm, Term } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sum a numeric term over the rows matched by the aggregate body. Complete
|
|
4
|
+
* the aggregate with `.where(body)` or `.where(body, { completeWhen })`.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* `completeWhen` attaches a completeness condition to the aggregate: while
|
|
8
|
+
* the condition is unmet, a below-threshold total stays unknown instead of
|
|
9
|
+
* collapsing to false, since more rows may still arrive.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* kb.rule(annualIncomeOver200k({ person: P, year: Y })).iff(
|
|
14
|
+
* letIn(
|
|
15
|
+
* Total,
|
|
16
|
+
* sum(Amount).where(incomeFact({ person: P, year: Y, amountCents: Amount }), {
|
|
17
|
+
* completeWhen: incomeReviewComplete({ person: P, year: Y }),
|
|
18
|
+
* }),
|
|
19
|
+
* gt(Total, 20_000_000),
|
|
20
|
+
* ),
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function sum(value: NumericTerm): AggregateBuilder<number | bigint>;
|
|
25
|
+
/**
|
|
26
|
+
* Count the rows matched by the aggregate body. Runs through the same
|
|
27
|
+
* aggregate machinery as {@link sum}, but thresholds the number of matching
|
|
28
|
+
* rows rather than a value total.
|
|
29
|
+
*/
|
|
30
|
+
export declare function count(value: Term): AggregateBuilder;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DateTerm, Formula, NumericTerm } from "../types.js";
|
|
2
|
+
/** Add two numeric terms, producing a numeric rule-body expression. */
|
|
3
|
+
export declare function add(left: NumericTerm, right: NumericTerm): Formula<number | bigint, readonly [NumericTerm, NumericTerm]>;
|
|
4
|
+
/**
|
|
5
|
+
* Subtract a numeric duration in milliseconds from a date term, producing a
|
|
6
|
+
* date expression.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // The visit must fall within the 14 days before consent.
|
|
11
|
+
* kb.rule(adequatePerformanceStatus({ patient: P, trial: T })).if(
|
|
12
|
+
* and(
|
|
13
|
+
* consentDate({ patient: P, trial: T, date: ConsentDate }),
|
|
14
|
+
* visitDate({ patient: P, date: VisitDate }),
|
|
15
|
+
* gte(VisitDate, sub(ConsentDate, 14 * dayMs)),
|
|
16
|
+
* lte(VisitDate, ConsentDate),
|
|
17
|
+
* ),
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function sub(left: DateTerm, right: NumericTerm): Formula<Date, readonly [DateTerm, NumericTerm]>;
|
|
22
|
+
/** Subtract one numeric term from another. */
|
|
23
|
+
export declare function sub(left: NumericTerm, right: NumericTerm): Formula<number | bigint, readonly [NumericTerm, NumericTerm]>;
|
|
24
|
+
/** Multiply two numeric terms, producing a numeric rule-body expression. */
|
|
25
|
+
export declare function mul(left: NumericTerm, right: NumericTerm): Formula<number | bigint, readonly [NumericTerm, NumericTerm]>;
|
|
26
|
+
/** Divide one numeric term by another, producing a numeric rule-body expression. */
|
|
27
|
+
export declare function div(left: NumericTerm, right: NumericTerm): Formula<number | bigint, readonly [NumericTerm, NumericTerm]>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComparableTerm, Formula } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Require the left term to be strictly greater than the right.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Comparisons are side constraints: they gate whether a rule fires but
|
|
7
|
+
* contribute no proof children of their own.
|
|
8
|
+
*/
|
|
9
|
+
export declare function gt(left: ComparableTerm, right: ComparableTerm): Formula<boolean, readonly [ComparableTerm, ComparableTerm]>;
|
|
10
|
+
/** Require the left term to be greater than or equal to the right. */
|
|
11
|
+
export declare function gte(left: ComparableTerm, right: ComparableTerm): Formula<boolean, readonly [ComparableTerm, ComparableTerm]>;
|
|
12
|
+
/** Require the left term to be less than or equal to the right. */
|
|
13
|
+
export declare function lte(left: ComparableTerm, right: ComparableTerm): Formula<boolean, readonly [ComparableTerm, ComparableTerm]>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ClosedWorldPredicate, OpenWorldPredicate, PredicateMetadata } from "../types/predicate.js";
|
|
2
|
+
import type { PredicateSchema } from "../types/schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Declare a closed-world predicate with named fields: absence of matching
|
|
5
|
+
* evidence counts as `false`, and `not(...)` uses negation-as-failure.
|
|
6
|
+
*/
|
|
7
|
+
export declare function closedPredicate<const Schema extends PredicateSchema>(name: string, params: Schema, metadata?: PredicateMetadata): ClosedWorldPredicate<Schema>;
|
|
8
|
+
/**
|
|
9
|
+
* Declare a predicate with named fields, open-world: absent evidence leaves
|
|
10
|
+
* queries `unknown` rather than `false` (use {@link closedPredicate} for
|
|
11
|
+
* closed-world semantics). The returned declaration is callable: calling it
|
|
12
|
+
* with field values or variables constructs an atom for use in evidence,
|
|
13
|
+
* rules, and queries.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* class Person implements Entity {
|
|
18
|
+
* constructor(readonly id: string) {}
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* const incomeFact = predicate("income_fact", {
|
|
22
|
+
* person: Person,
|
|
23
|
+
* year: Number,
|
|
24
|
+
* amountCents: Number,
|
|
25
|
+
* });
|
|
26
|
+
* const alice = new Person("alice");
|
|
27
|
+
* const Amount = variable("Amount");
|
|
28
|
+
*
|
|
29
|
+
* const evidence = incomeFact({ person: alice, year: 2024, amountCents: 24_680_000 });
|
|
30
|
+
* const pattern = incomeFact({ person: alice, year: 2024, amountCents: Amount });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function predicate<const Schema extends PredicateSchema>(name: string, params: Schema, metadata?: PredicateMetadata): OpenWorldPredicate<Schema>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Atom } from "../types/atom.js";
|
|
2
|
+
import type { AnyLiteral, Literal } from "../types/literal.js";
|
|
3
|
+
import type { Entity, PredicateSchema } from "../types/schema.js";
|
|
4
|
+
import type { Rule } from "../types/rule.js";
|
|
5
|
+
import type { Var } from "../types/variable.js";
|
|
6
|
+
export type { Entity };
|
|
7
|
+
/** Predicate over stored literal handles. */
|
|
8
|
+
export type LiteralFilter = (literal: AnyLiteral) => boolean;
|
|
9
|
+
/** A scalar value accepted in example expressions. */
|
|
10
|
+
export type Scalar = string | number | bigint | boolean | Date;
|
|
11
|
+
/** A concrete value or logic variable used in formulas and examples. */
|
|
12
|
+
export type Term<Value = Scalar | Entity> = Value | Var;
|
|
13
|
+
/**
|
|
14
|
+
* A rule-body expression descriptor built by the DSL operators (`and`, `or`,
|
|
15
|
+
* `exists`, `letIn`, arithmetic, comparisons, aggregates) and evaluated by
|
|
16
|
+
* the solver during query.
|
|
17
|
+
*/
|
|
18
|
+
export type Formula<Value, Args extends readonly unknown[]> = Readonly<{
|
|
19
|
+
readonly kind: string;
|
|
20
|
+
readonly args: Args;
|
|
21
|
+
readonly __valueType?: Value;
|
|
22
|
+
}>;
|
|
23
|
+
/** Return whether an unknown value has the runtime shape of a formula. */
|
|
24
|
+
export declare function isFormula(value: unknown, kind?: string): value is Formula<unknown, readonly unknown[]>;
|
|
25
|
+
export type FormulaArgumentInput<Value> = Value extends Scalar | Entity | Var ? Value : Value extends Atom<infer Schema extends PredicateSchema> ? Atom<Schema> : Value extends Literal<infer Schema extends PredicateSchema> ? Literal<Schema> : Value extends Formula<infer FormulaValue, infer Args> ? Formula<FormulaValue, Args> : never;
|
|
26
|
+
/** Preserve the concrete schema of a valid formula, atom, or literal rule body. */
|
|
27
|
+
export type RuleBodyInput<Body> = Body extends Formula<infer Value, infer Args> ? Formula<Value, Args> : Body extends Atom<infer Schema extends PredicateSchema> ? Atom<Schema> : Body extends Literal<infer Schema extends PredicateSchema> ? Literal<Schema> : never;
|
|
28
|
+
export type RuleBodyArgs<Args extends readonly unknown[]> = {
|
|
29
|
+
readonly [Index in keyof Args]: RuleBodyInput<Args[Index]>;
|
|
30
|
+
};
|
|
31
|
+
type ExistsArgumentInput<Value> = Value extends Var ? Value : RuleBodyInput<Value>;
|
|
32
|
+
export type ExistsArgs<Args extends readonly unknown[]> = {
|
|
33
|
+
readonly [Index in keyof Args]: ExistsArgumentInput<Args[Index]>;
|
|
34
|
+
};
|
|
35
|
+
/** Preserve a literal's schema while rejecting non-literal arguments. */
|
|
36
|
+
type LiteralInput<Value> = Value extends Literal<infer Schema extends PredicateSchema> ? Literal<Schema> : never;
|
|
37
|
+
/** Validate each literal in a variadic argument list without erasing its schema. */
|
|
38
|
+
export type LiteralArgs<Args extends readonly unknown[]> = {
|
|
39
|
+
readonly [Index in keyof Args]: LiteralInput<Args[Index]>;
|
|
40
|
+
};
|
|
41
|
+
/** Numeric term accepted by arithmetic, comparisons, and aggregate helpers. */
|
|
42
|
+
export type NumericTerm = number | bigint | Var | Formula<number | bigint, readonly unknown[]>;
|
|
43
|
+
/** Date term accepted by date comparisons and date-offset arithmetic. */
|
|
44
|
+
export type DateTerm = Date | Var | Formula<Date, readonly unknown[]>;
|
|
45
|
+
/** Term accepted by comparison helpers. */
|
|
46
|
+
export type ComparableTerm = NumericTerm | DateTerm;
|
|
47
|
+
/** Aggregate builder returned by sum(...) and count(...). */
|
|
48
|
+
export type AggregateBuilder<Value extends number | bigint = number> = Readonly<{
|
|
49
|
+
where: (<const Body>(body: Body & RuleBodyInput<Body>) => Formula<Value, readonly [Body]>) & (<const Body, const CompleteWhen>(body: Body & RuleBodyInput<Body>, options: Readonly<{
|
|
50
|
+
completeWhen: CompleteWhen & RuleBodyInput<CompleteWhen>;
|
|
51
|
+
}>) => Formula<Value, readonly [Body, Readonly<{
|
|
52
|
+
completeWhen: CompleteWhen;
|
|
53
|
+
}>]>);
|
|
54
|
+
}>;
|
|
55
|
+
/** Rule builder returned by KnowledgeBase.rule(...). */
|
|
56
|
+
export type RuleBuilder<Schema extends PredicateSchema> = Readonly<{
|
|
57
|
+
if: <const Body>(body: Body & RuleBodyInput<Body>) => Rule<Schema, Body>;
|
|
58
|
+
iff: <const Body>(body: Body & RuleBodyInput<Body>) => Rule<Schema, Body>;
|
|
59
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ArithmeticGoal } from "../types/goal.js";
|
|
2
|
+
import type { PredicateDeclaration } from "../types/predicate.js";
|
|
3
|
+
import type { AnyRule } from "../types/rule.js";
|
|
4
|
+
import type { Var } from "../types/variable.js";
|
|
5
|
+
/** Base class for errors that mean the caller supplied invalid program logic. */
|
|
6
|
+
export declare class ProgrammerError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
/** Arithmetic primitive with operands the solver cannot legally evaluate. */
|
|
10
|
+
export declare class PrimitiveArithmeticProgrammerError extends ProgrammerError {
|
|
11
|
+
/** The arithmetic operation that failed. */
|
|
12
|
+
readonly operation: "add" | "sub" | "mul" | "div";
|
|
13
|
+
/** The evaluated left and right operand values. */
|
|
14
|
+
readonly operands: readonly [unknown, unknown];
|
|
15
|
+
/** The arithmetic goal being evaluated when the failure occurred. */
|
|
16
|
+
readonly goal: ArithmeticGoal<"add" | "sub" | "mul" | "div">;
|
|
17
|
+
constructor(operation: "add" | "sub" | "mul" | "div", operands: readonly [unknown, unknown], goal: ArithmeticGoal<"add" | "sub" | "mul" | "div">);
|
|
18
|
+
}
|
|
19
|
+
/** Rule body uses variables before any constructive source can bind them. */
|
|
20
|
+
export declare class UnsafeUnboundVariableProgrammerError extends ProgrammerError {
|
|
21
|
+
/** The variables used before any constructive source could bind them. */
|
|
22
|
+
readonly variables: readonly Var[];
|
|
23
|
+
constructor(variables: readonly Var[]);
|
|
24
|
+
}
|
|
25
|
+
/** Recursive program structure outside the solver's constructive fragment. */
|
|
26
|
+
export declare class UnconstructiveRecursionProgrammerError extends ProgrammerError {
|
|
27
|
+
constructor();
|
|
28
|
+
}
|
|
29
|
+
/** Predicate has a complete iff definition and additional competing rules. */
|
|
30
|
+
export declare class ExclusiveDefinitionProgrammerError extends ProgrammerError {
|
|
31
|
+
/** The predicate holding the exclusive `iff` definition. */
|
|
32
|
+
readonly predicate: PredicateDeclaration;
|
|
33
|
+
/** The competing rules that conflict with the `iff` definition. */
|
|
34
|
+
readonly rules: readonly AnyRule[];
|
|
35
|
+
constructor(predicate: PredicateDeclaration, rules: readonly AnyRule[]);
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Judgment } from "../types/judgment.js";
|
|
2
|
+
import type { Explanation } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert a judgment's proof-support tree into a compact, LLM-readable
|
|
5
|
+
* {@link Explanation} of what was concluded and why.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const explanation = explainJudgment(query(kb, doctor({ person: alice })));
|
|
10
|
+
* ```
|
|
11
|
+
* Yields nested explanation nodes such as:
|
|
12
|
+
* ```json
|
|
13
|
+
* {
|
|
14
|
+
* "claim": "doctor(alice) = true",
|
|
15
|
+
* "because": "doctor(P) iff and(passed_medical_school(P), has_active_license(P)), with P = alice",
|
|
16
|
+
* "given": [{ "claim": "and(...) = true" }]
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function explainJudgment(judgment: Judgment): Explanation;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explanations: convert a query judgment's proof tree into a compact,
|
|
3
|
+
* LLM-readable claim/because/given structure.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { explainJudgment } from "./explain.js";
|
|
8
|
+
export type { CompoundExplanation, DerivedExplanation, EvidenceExplanation, EvidenceReason, Explanation, ExplanationBase, TerminalExplanation, } from "./types.js";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFormula
|
|
3
|
+
} from "../chunk-YF4PP42K.js";
|
|
4
|
+
import {
|
|
5
|
+
AssertionError,
|
|
6
|
+
isVar
|
|
7
|
+
} from "../chunk-UVOCOH2I.js";
|
|
8
|
+
|
|
9
|
+
// src/explanation/explain.ts
|
|
10
|
+
function explainJudgment(judgment) {
|
|
11
|
+
if (isClosedWorldAbsence(judgment)) {
|
|
12
|
+
const atom = judgment.goal.goal.atom;
|
|
13
|
+
return {
|
|
14
|
+
claim: claimForJudgment(judgment),
|
|
15
|
+
because: `${formatAtom(atom)} is closed-world, and no evidence is known`
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const supports = supportsForValue(judgment);
|
|
19
|
+
const ruleSupport = supports.find((support) => support.source === "rule");
|
|
20
|
+
if (ruleSupport !== void 0) {
|
|
21
|
+
return {
|
|
22
|
+
claim: claimForJudgment(judgment),
|
|
23
|
+
because: formatRuleApplication(ruleSupport.rule, ruleSupport.body.bindings),
|
|
24
|
+
given: [explainJudgment(ruleSupport.body)]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const evidence = supports.flatMap(
|
|
28
|
+
(support) => support.source === "evidence" ? support.literals : []
|
|
29
|
+
);
|
|
30
|
+
if (evidence.length > 0) {
|
|
31
|
+
const reasons = evidence.map(evidenceReason);
|
|
32
|
+
const firstReason = reasons[0];
|
|
33
|
+
return {
|
|
34
|
+
claim: claimForJudgment(judgment),
|
|
35
|
+
because: reasons.length === 1 && firstReason !== void 0 ? firstReason : reasons
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const subgoals = supports.flatMap(
|
|
39
|
+
(support) => support.source === "subgoals" ? support.judgments : []
|
|
40
|
+
);
|
|
41
|
+
if (subgoals.length > 0) {
|
|
42
|
+
return {
|
|
43
|
+
claim: claimForJudgment(judgment),
|
|
44
|
+
given: uniqueExplanations(subgoals.map(explainJudgment))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const closedWorldAbsence = supports.find((support) => support.source === "closed-world-absence");
|
|
48
|
+
if (closedWorldAbsence !== void 0 && judgment.goal.kind === "atom") {
|
|
49
|
+
return {
|
|
50
|
+
claim: claimForJudgment(judgment),
|
|
51
|
+
because: `${formatAtom(judgment.goal.atom)} is closed-world, and no matching evidence is known`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
claim: claimForJudgment(judgment),
|
|
56
|
+
because: "no supporting evidence or rule was found"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function uniqueExplanations(explanations) {
|
|
60
|
+
const seen = /* @__PURE__ */ new Set();
|
|
61
|
+
const unique = [];
|
|
62
|
+
for (const explanation of explanations) {
|
|
63
|
+
const key = JSON.stringify(explanation);
|
|
64
|
+
if (!seen.has(key)) {
|
|
65
|
+
seen.add(key);
|
|
66
|
+
unique.push(explanation);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return unique;
|
|
70
|
+
}
|
|
71
|
+
function supportsForValue(judgment) {
|
|
72
|
+
if (judgment.value === "true") {
|
|
73
|
+
return judgment.positive;
|
|
74
|
+
}
|
|
75
|
+
if (judgment.value === "false") {
|
|
76
|
+
return judgment.negative;
|
|
77
|
+
}
|
|
78
|
+
if (judgment.value === "conflicted") {
|
|
79
|
+
return [...judgment.positive, ...judgment.negative];
|
|
80
|
+
}
|
|
81
|
+
return judgment.inconclusive ?? [];
|
|
82
|
+
}
|
|
83
|
+
function isClosedWorldAbsence(judgment) {
|
|
84
|
+
if (judgment.goal.kind !== "not" || judgment.goal.goal.kind !== "atom") {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (judgment.value !== "true") {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const [support] = judgment.positive;
|
|
91
|
+
const [child] = support?.source === "subgoals" ? support.judgments : [];
|
|
92
|
+
if (child === void 0 || judgment.goal.goal.atom.predicate.world !== "closed") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (child.value === "unknown") {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return child.value === "false" && child.negative.some((childSupport) => childSupport.source === "closed-world-absence");
|
|
99
|
+
}
|
|
100
|
+
function claimForJudgment(judgment) {
|
|
101
|
+
return `${formatGoal(judgment.goal)} = ${judgment.value}`;
|
|
102
|
+
}
|
|
103
|
+
function evidenceReason(literal) {
|
|
104
|
+
const claim = `${formatAtom(literal.atom)} = ${literal.sign === "+" ? "true" : "false"}`;
|
|
105
|
+
const source = literal.metadata?.source;
|
|
106
|
+
return source === void 0 ? { claim } : { claim, source };
|
|
107
|
+
}
|
|
108
|
+
function formatRuleApplication(rule, bindings) {
|
|
109
|
+
const base = `${formatLiteral(rule.head)} ${rule.kind} ${formatRuleBody(rule.body)}`;
|
|
110
|
+
if (bindings.length === 0) {
|
|
111
|
+
return base;
|
|
112
|
+
}
|
|
113
|
+
return `${base}, with ${bindings.map(([variable, value]) => `${variable.name} = ${formatValue(value)}`).join(", ")}`;
|
|
114
|
+
}
|
|
115
|
+
function formatRuleBody(body) {
|
|
116
|
+
if (isLiteral(body)) {
|
|
117
|
+
return formatLiteral(body);
|
|
118
|
+
}
|
|
119
|
+
if (isAtom(body)) {
|
|
120
|
+
return formatAtom(body);
|
|
121
|
+
}
|
|
122
|
+
if (isFormula(body)) {
|
|
123
|
+
return `${body.kind}(${body.args.map(formatRuleBody).join(", ")})`;
|
|
124
|
+
}
|
|
125
|
+
return formatValue(body);
|
|
126
|
+
}
|
|
127
|
+
function formatLiteral(literal) {
|
|
128
|
+
return literal.sign === "-" ? `not(${formatAtom(literal.atom)})` : formatAtom(literal.atom);
|
|
129
|
+
}
|
|
130
|
+
function formatGoal(goal) {
|
|
131
|
+
switch (goal.kind) {
|
|
132
|
+
case "atom":
|
|
133
|
+
return formatAtom(goal.atom);
|
|
134
|
+
case "not":
|
|
135
|
+
return `not(${formatGoal(goal.goal)})`;
|
|
136
|
+
case "and":
|
|
137
|
+
case "or":
|
|
138
|
+
return `${goal.kind}(${goal.goals.map(formatGoal).join(", ")})`;
|
|
139
|
+
case "exists":
|
|
140
|
+
return `exists(${goal.variables.map((variable) => variable.name).join(", ")}: ${formatGoal(goal.goal)})`;
|
|
141
|
+
case "let":
|
|
142
|
+
return `let(${goal.variable.name} = ${formatValue(goal.value)}: ${formatGoal(goal.goal)})`;
|
|
143
|
+
case "add":
|
|
144
|
+
case "sub":
|
|
145
|
+
case "mul":
|
|
146
|
+
case "div":
|
|
147
|
+
return `${goal.bind.name} = ${goal.kind}(${formatValue(goal.left)}, ${formatValue(goal.right)})`;
|
|
148
|
+
case "eq":
|
|
149
|
+
case "neq":
|
|
150
|
+
case "lt":
|
|
151
|
+
case "lte":
|
|
152
|
+
case "gt":
|
|
153
|
+
case "gte":
|
|
154
|
+
return `${goal.kind}(${formatValue(goal.left)}, ${formatValue(goal.right)})`;
|
|
155
|
+
case "sum":
|
|
156
|
+
return `${goal.bind.name} = sum(${formatValue(goal.value)})`;
|
|
157
|
+
case "count":
|
|
158
|
+
return `${goal.bind.name} = count(${formatGoal(goal.where)})`;
|
|
159
|
+
default:
|
|
160
|
+
throw new AssertionError("Unhandled goal kind");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function formatAtom(atom) {
|
|
164
|
+
const values = atom.predicate.paramNames.map((name) => formatValue(atom.fields[name]));
|
|
165
|
+
return `${atom.predicate.name}(${values.join(", ")})`;
|
|
166
|
+
}
|
|
167
|
+
function formatValue(value) {
|
|
168
|
+
if (isVar(value)) {
|
|
169
|
+
return value.name;
|
|
170
|
+
}
|
|
171
|
+
if (typeof value === "object" && value !== null && "id" in value && typeof value.id === "string") {
|
|
172
|
+
return value.id;
|
|
173
|
+
}
|
|
174
|
+
if (value instanceof Date) {
|
|
175
|
+
return value.toISOString();
|
|
176
|
+
}
|
|
177
|
+
return String(value);
|
|
178
|
+
}
|
|
179
|
+
function isLiteral(value) {
|
|
180
|
+
return typeof value === "object" && value !== null && "sign" in value && "atom" in value && (value.sign === "+" || value.sign === "-");
|
|
181
|
+
}
|
|
182
|
+
function isAtom(value) {
|
|
183
|
+
return typeof value === "object" && value !== null && "predicate" in value && "fields" in value;
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
explainJudgment
|
|
187
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** Shared shape for every compact, LLM-readable explanation node. */
|
|
2
|
+
export type ExplanationBase = Readonly<{
|
|
3
|
+
/** The evaluated claim and its value, e.g. `eligible(alice) = true`. */
|
|
4
|
+
claim: string;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* A compact, LLM-readable explanation node.
|
|
8
|
+
*
|
|
9
|
+
* Example intended contents for a derived claim:
|
|
10
|
+
*
|
|
11
|
+
* ```json
|
|
12
|
+
* {
|
|
13
|
+
* "claim": "eligible(alice) = true",
|
|
14
|
+
* "because": "eligible(P) iff not(active_sanction(P)), with P = alice",
|
|
15
|
+
* "given": [
|
|
16
|
+
* {
|
|
17
|
+
* "claim": "not(active_sanction(alice)) = true",
|
|
18
|
+
* "because": "active_sanction(alice) is closed-world, and no evidence is known"
|
|
19
|
+
* }
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export type Explanation = EvidenceExplanation | DerivedExplanation | CompoundExplanation | TerminalExplanation;
|
|
25
|
+
/**
|
|
26
|
+
* A claim explained directly by accepted evidence.
|
|
27
|
+
*
|
|
28
|
+
* Example intended contents for one supporting source:
|
|
29
|
+
*
|
|
30
|
+
* ```json
|
|
31
|
+
* {
|
|
32
|
+
* "claim": "active_sanction(bob) = true",
|
|
33
|
+
* "because": {
|
|
34
|
+
* "source": "sanctions_registry",
|
|
35
|
+
* "quote": "Bob appears on the active sanctions list."
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Example intended contents for conflicting evidence:
|
|
41
|
+
*
|
|
42
|
+
* ```json
|
|
43
|
+
* {
|
|
44
|
+
* "claim": "active_sanction(drew) = conflicted",
|
|
45
|
+
* "because": [
|
|
46
|
+
* {
|
|
47
|
+
* "claim": "active_sanction(drew) = true",
|
|
48
|
+
* "source": "sanctions_registry",
|
|
49
|
+
* "quote": "Drew appears on the active sanctions list."
|
|
50
|
+
* },
|
|
51
|
+
* {
|
|
52
|
+
* "claim": "active_sanction(drew) = false",
|
|
53
|
+
* "source": "appeals_board",
|
|
54
|
+
* "quote": "Drew's sanction was overturned on appeal."
|
|
55
|
+
* }
|
|
56
|
+
* ]
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export type EvidenceExplanation = ExplanationBase & Readonly<{
|
|
61
|
+
because: EvidenceReason | readonly EvidenceReason[];
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* A claim explained by applying a rule to subclaims.
|
|
65
|
+
*
|
|
66
|
+
* Example intended contents:
|
|
67
|
+
*
|
|
68
|
+
* ```json
|
|
69
|
+
* {
|
|
70
|
+
* "claim": "eligible(bob) = false",
|
|
71
|
+
* "because": "eligible(P) iff not(active_sanction(P)), with P = bob",
|
|
72
|
+
* "given": [
|
|
73
|
+
* {
|
|
74
|
+
* "claim": "not(active_sanction(bob)) = false",
|
|
75
|
+
* "given": [
|
|
76
|
+
* {
|
|
77
|
+
* "claim": "active_sanction(bob) = true",
|
|
78
|
+
* "because": {
|
|
79
|
+
* "source": "sanctions_registry",
|
|
80
|
+
* "quote": "Bob appears on the active sanctions list."
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* ]
|
|
84
|
+
* }
|
|
85
|
+
* ]
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export type DerivedExplanation = ExplanationBase & Readonly<{
|
|
90
|
+
because: string;
|
|
91
|
+
given: readonly Explanation[];
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* A claim explained by combining subclaims, such as an `and`, `or`, or `not` goal.
|
|
95
|
+
*
|
|
96
|
+
* Example intended contents:
|
|
97
|
+
*
|
|
98
|
+
* ```json
|
|
99
|
+
* {
|
|
100
|
+
* "claim": "not(active_sanction(drew)) = conflicted",
|
|
101
|
+
* "given": [
|
|
102
|
+
* {
|
|
103
|
+
* "claim": "active_sanction(drew) = conflicted",
|
|
104
|
+
* "because": [
|
|
105
|
+
* {
|
|
106
|
+
* "claim": "active_sanction(drew) = true",
|
|
107
|
+
* "source": "sanctions_registry",
|
|
108
|
+
* "quote": "Drew appears on the active sanctions list."
|
|
109
|
+
* },
|
|
110
|
+
* {
|
|
111
|
+
* "claim": "active_sanction(drew) = false",
|
|
112
|
+
* "source": "appeals_board",
|
|
113
|
+
* "quote": "Drew's sanction was overturned on appeal."
|
|
114
|
+
* }
|
|
115
|
+
* ]
|
|
116
|
+
* }
|
|
117
|
+
* ]
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export type CompoundExplanation = ExplanationBase & Readonly<{
|
|
122
|
+
because?: string;
|
|
123
|
+
given: readonly Explanation[];
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* A terminal non-evidence explanation, such as closed-world absence or an unknown.
|
|
127
|
+
*
|
|
128
|
+
* Example intended contents:
|
|
129
|
+
*
|
|
130
|
+
* ```json
|
|
131
|
+
* {
|
|
132
|
+
* "claim": "not(active_sanction(alice)) = true",
|
|
133
|
+
* "because": "active_sanction(alice) is closed-world, and no evidence is known"
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export type TerminalExplanation = ExplanationBase & Readonly<{
|
|
138
|
+
because: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Evidence metadata embedded directly under `because`.
|
|
142
|
+
*
|
|
143
|
+
* Example intended contents for a single source:
|
|
144
|
+
*
|
|
145
|
+
* ```json
|
|
146
|
+
* {
|
|
147
|
+
* "source": "sanctions_registry",
|
|
148
|
+
* "quote": "Bob appears on the active sanctions list."
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* Example intended contents when the evidence reason names its side explicitly:
|
|
153
|
+
*
|
|
154
|
+
* ```json
|
|
155
|
+
* {
|
|
156
|
+
* "claim": "active_sanction(drew) = false",
|
|
157
|
+
* "source": "appeals_board",
|
|
158
|
+
* "quote": "Drew's sanction was overturned on appeal.",
|
|
159
|
+
* "metadata": { "reviewed_by": "appeals_panel" }
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export type EvidenceReason = Readonly<{
|
|
164
|
+
/** Optional when the surrounding `claim` already names what the evidence proves. */
|
|
165
|
+
claim?: string;
|
|
166
|
+
/** Where the evidence came from, e.g. `sanctions_registry`. */
|
|
167
|
+
source?: string;
|
|
168
|
+
/** The quoted text or excerpt that supports the evidence claim. */
|
|
169
|
+
quote?: string;
|
|
170
|
+
/** Raw source-specific details that should not be flattened into prose. */
|
|
171
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
172
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The knowledge base: store signed evidence literals, declare derivation
|
|
3
|
+
* rules, and register domain entities.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { ExclusiveDefinitionProgrammerError, ProgrammerError, PrimitiveArithmeticProgrammerError, UnsafeUnboundVariableProgrammerError, } from "../exceptions/programmer-errors.js";
|
|
8
|
+
export type { EvidenceMetadata } from "../types/evidence-metadata.js";
|
|
9
|
+
export type { Annotations, AnnotationValue } from "../types/annotations.js";
|
|
10
|
+
export type { AnyLiteral, Literal, LiteralSign, NegativeLiteral, PositiveLiteral, } from "../types/literal.js";
|
|
11
|
+
export type { AnyRule, Rule, RuleKind } from "../types/rule.js";
|
|
12
|
+
export type { LiteralFilter, RuleBuilder } from "../dsl/types.js";
|
|
13
|
+
export { EntityRegistry, KnowledgeBase } from "./knowledge-base.js";
|