factbag 0.0.0-stage → 0.1.0-alpha.7

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 ADDED
@@ -0,0 +1,108 @@
1
+ # factbag
2
+
3
+ ## 0.1.0-alpha.7
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.0.0-alpha.6
8
+
9
+ ### Minor Changes
10
+
11
+ - 7225a51: Add `firstOfMonth(year, month)`, a date-valued arithmetic term for the first day of a month at UTC midnight, and `yearOf(date)`, a number-valued term for a date's UTC calendar year. Rules can now derive a year's bounds, such as `firstOfMonth(Y, 1)` and `sub(firstOfMonth(add(Y, 1), 1), 86_400_000)`, instead of looking them up in asserted calendar facts. A month outside 1 to 12, or a year that is not a whole number, throws `PrimitiveArithmeticProgrammerError` rather than rolling over. The query API adds `FirstOfMonthGoal` and the `"firstOfMonth"` arithmetic kind.
12
+ - 5114b2f: Show the `citation` and `justification` recorded with a piece of evidence in its explanation, beside its `source`. An evidence reason no longer repeats the claim of the step it supports; it names its own claim only when that differs, as in a conflict, or when the evidence has no provenance. `EvidenceReason.quote`, which was never filled, is replaced by `citation`.
13
+ - eab3679: Explain a rule application with the rule alone, `eligible(P) iff not(active_sanction(P))`, without the trailing `, with P = alice`. The values a rule was applied to are already written into the claim it supports and the claims it rests on.
14
+ - e4b343e: Add a `budget` option to `explainJudgment`: the most claims to explain. The conclusion is always explained, then the claims that decided it before those that were evaluated and changed nothing, and those near the conclusion before those far from it. A claim that rests on claims left out names them, with their values, under `notShown`. Without a budget the whole proof is explained, as before; written as JSON a deep proof repeats each shared claim on every path to it, so give a budget when explaining a judgment in text.
15
+
16
+ ### Patch Changes
17
+
18
+ - 7f26b45: Write explanation claims with the values their variables hold: `lte(1000, 36900) = true` instead of `lte(I, 36900) = true`, and `spouse(alice, S) = false` instead of `spouse(P, S) = false`. A variable with no value keeps its name. A term that computes a value states it, such as `H = max(1000, 500) = 1000`, instead of ending in `= true`.
19
+ - b7c85ff: Explain a comparison, arithmetic term or aggregate as `computed` instead of `no supporting evidence or rule was found`. When one could not be evaluated because a variable has no value, the explanation names the variable, such as `X has no value`.
20
+ - ed6ec84: Write a date at UTC midnight as a day in explanations, `2015-12-31` instead of `2015-12-31T00:00:00.000Z`. A date with a time of day keeps its full timestamp.
21
+ - 15391f2: Rest a rule application directly on the subgoals of its body in explanations. When a rule's body is an `and` or an `or`, the explanation no longer has a node that restates the body between the rule and its subgoals: the rule's `given` lists the subgoals themselves. A compound nested inside a body is still its own node, and so is a body that is a single atom or a negation.
22
+ - ecbd8d9: Stop naming the solver's temporaries, such as `$tmp0`, in explanations. A nested arithmetic term is written as the term and its value, `mul(1000, 0.15) = 150`; a term that could not be computed stands in for its result wherever that is used, `gte(add(X, 1), 18) = unknown`; and a rule's `with ...` list gives only the rule's own variables.
23
+
24
+ ## 0.0.0-alpha.5
25
+
26
+ ### Minor Changes
27
+
28
+ - 19ac508: **Breaking:** errors have their own entry point, `factbag/exceptions`. `ProgrammerError`, `ExclusiveDefinitionProgrammerError`, `PrimitiveArithmeticProgrammerError`, and `UnsafeUnboundVariableProgrammerError` move there from `factbag/kb`. `UnconstructiveRecursionProgrammerError`, which the solver raised but no entry point exported, is now exported too. `UnsupportedPredicateError` moves there from `factbag/jev/errors` and now extends `ProgrammerError`.
29
+ - 19ac508: **Breaking:** Jev moves from `factbag/jev` to its own package, `@factbag/jev`. Install it alongside `factbag` and `@typesafe-ai/sdk`, which is now a required peer dependency at `^0.7.0`, and import `classifier`, `resolver`, `createConfig`, `Config`, and the `DEFAULT_*` constants from `@factbag/jev`. `factbag` no longer has a Jev entry point or an SDK peer dependency. `@factbag/jev` and `@factbag/clef` are released together with `factbag` and require the same `factbag` version.
30
+
31
+ **Breaking:** `@factbag/jev` has a single entry point. `factbag/jev/classifier`, `factbag/jev/resolve`, `factbag/jev/config`, and `factbag/jev/errors` have no replacement subpaths.
32
+
33
+ Add `batchSize` to Jev's `Config`: how many questions each request holds. It defaults to 64 and has no upper limit. An invalid `batchSize` throws when the config is created or a run is built.
34
+
35
+ Jev's `createConfig` now returns `Normalized & Config`, so `client` is no longer guaranteed on the result.
36
+
37
+ - dd3730b: **Breaking:** `factbag/guards` and `factbag/query` are removed. Import `guard`, `GuardedKB`, and the other guard exports from `factbag/kb`, and `query`, `queryAll`, `Judgment`, and the goal and support types from `factbag/dsl`.
38
+
39
+ ## 0.0.0-alpha.4
40
+
41
+ ### Minor Changes
42
+
43
+ - 2814aab: Allow enumerations to hold numbers or dates as well as strings, with one member
44
+ type per enumeration. Members must be unique; dates compare by instant and are
45
+ copied on construction. NaN and invalid dates are rejected. Enumeration membership
46
+ validation at assertion and query time is unchanged.
47
+ - 1f5bb1b: Classify predicates whose fields are number or Date enumerations, alongside
48
+ string enumerations. Dates are sent to the model as ISO strings and retained as
49
+ `Date` values in evidence. Open-ended `String`, `Number`, and `Date` fields
50
+ remain unsupported.
51
+ - 9ca1766: Reorganize classification and resolution to separate evidence workflows from model requests. The workflows own evidence admission and knowledge-base updates; the Jev implementation owns prompts, encoding, batching, and response validation.
52
+
53
+ **Breaking changes:** call the classifier with `classifier({ corpus, input, config })`. Classification and resolution jobs now expose only `execute(): Promise<void>`; read the resulting knowledge base from `run.kb`. Job metadata and assessment and resolution reports are removed. Replace `RequestConfig`, `ResolutionConfig`, and nested `AdmissionPolicy` settings with the shared, flat `Config`. Pass an SDK client directly instead of implementing the removed custom client interfaces.
54
+
55
+ Classification now executes one job per chunk across all permitted predicates. All responses for the chunk must validate before its evidence is written. Failed jobs can be retried without partial evidence, repeating the chunk's requests; successful repeats add fresh evidence.
56
+
57
+ Optionally filter candidates by passage relevance with `config: { routing: true }`. Each chunk first asks which predicates the passage may bear on. For predicates that make claims about supplied entities or values, it can also filter those symbols before combining them into complete candidate facts. `candidateProductCutoff` controls when this extra filtering runs. Filtering defaults to off: false negatives can suppress evidence, including denials, and the extra requests do not guarantee cost savings.
58
+
59
+ Expose `createConfig`, `Config`, and shared `DEFAULT_*` constants through `factbag/jev/config`. Normalize settings once and preserve a shared lazy client when a factory-created config is reused across workflows.
60
+
61
+ Export `fieldEquals` from `factbag/dsl` and use core equality in Jev: entities compare by constructor and ID, dates by instant. Resolution now groups contradictory evidence about separate instances of the same entity.
62
+
63
+ Encode candidate values when building requests, using the original entities and dates instead of construction-time JSON snapshots. Candidate domains remain fixed at run construction.
64
+
65
+ ## 0.0.0-alpha.3
66
+
67
+ ### Minor Changes
68
+
69
+ - f8f2712: Add `eq` and `neq` rule-body comparisons for numbers, dates, strings, booleans, and entities. Entities compare by type and id rather than instance; dates compare by instant.
70
+ - 900b104: Add min, max, roundTo, floorTo, and ceilTo numeric arithmetic terms for rule bodies. Rounding supports whole-number decimal places (including negative places), with halves rounded away from zero. Decimal exponent shifting avoids multiplication artifacts at decimal rounding boundaries.
71
+ - 4f040e3: Add `daysBetween(later, earlier)` and `yearsBetween(later, earlier)` as number-valued arithmetic terms over dates. They count signed UTC calendar days and whole anniversary years, ignoring time of day; February 29 anniversaries fall on March 1 in common years. Numeric subtraction on variables now composes with other numeric operators.
72
+
73
+ ### Patch Changes
74
+
75
+ - 01cc338: Speed up queries over large rulesets with linear-time recursive predicate grouping,
76
+ per-query rule and evidence indexes, and judgment reuse during candidate discovery.
77
+ Preserve candidate pruning and distinguish same-named predicate declarations when
78
+ tracking active atoms, avoiding incorrect recursion cutoffs.
79
+ - 80d1e75: Share explanation nodes for reused proof judgments so explanation construction no longer grows with the number of paths through a shared proof. Identical sub-explanations are still listed once, and rendered content is unchanged.
80
+ - a074dab: Judge evidence-free, closed-world recursive components together as a least fixed
81
+ point instead of enumerating recursive proof paths. Doubly recursive transitive
82
+ closures and symmetric compositions no longer require exponential proof search.
83
+ Other components retain the existing search semantics. Variable queries recognize
84
+ a non-recursive disjunctive base case within an exclusive definition.
85
+
86
+ ## 0.0.0-alpha.2
87
+
88
+ ### Major Changes
89
+
90
+ - e212a94: Remove bigint support from predicate schemas, numeric expressions, aggregates, and numeric proof ranges. Numeric values now use JavaScript number exclusively, and an empty sum returns 0. The DSL rejects BigInt schemas and bigint operands rather than coercing them.
91
+
92
+ Migrate BigInt fields to Number and bigint literals to numbers. Division now uses number semantics (fractional results rather than bigint truncation); values outside the safe integer range can lose precision. Date support is unchanged.
93
+
94
+ ### Patch Changes
95
+
96
+ - bc114ed: Add npm alpha version, CI status, and license badges to the README.
97
+ - a3583ab: Publish the full project README on npm, including the document-to-conclusion walkthrough.
98
+
99
+ ## 0.0.0-alpha.1
100
+
101
+ ### Patch Changes
102
+
103
+ - ebc4a8a: A `letIn` or aggregate whose variable is already bound now constrains that variable instead of rebinding it. Before, a rule such as `tax(P, R) iff letIn(R, ...)` concluded `tax(alice, 99)` as true when its body computed 98, and an `or` branch evaluated under another branch's bindings reported those bindings as true. Proofs for such queries now carry a temporary variable and an `eq` comparison.
104
+ - eca65c2: An `or` is now judged per binding environment, so a branch that fails without binding a variable can no longer borrow the truth of a sibling branch whose bindings were discarded. Before, `and(or(p(X), r()), s(X))` could be judged true when `p(x1)` and `s(x3)` held but nothing satisfied both: the false `r()` branch produced an environment without `X`, the `or` was judged true there via `p(x1)`, and `s(X)` then ranged over every candidate. The recursion validator's edge collector also now carries variables bound in every `or` branch forward, matching the variable-use checker, so a comparison after such an `or` is no longer reported as unconstructive recursion.
105
+
106
+ ## 0.0.0-alpha.0
107
+
108
+ Initial experimental distribution of core, corpus, guards, and Jev functionality.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Daniel du Plessis
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,309 @@
1
- # Temporary Holding Version
1
+ # Factbag
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm alpha](https://img.shields.io/npm/v/factbag/alpha)](https://www.npmjs.com/package/factbag)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/factbag/factbag/ci.yml?branch=main&event=push&label=CI)](https://github.com/factbag/factbag/actions/workflows/ci.yml?query=branch%3Amain)
5
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/factbag/factbag/blob/main/LICENSE)
6
+
7
+ <img src="https://raw.githubusercontent.com/factbag/factbag/main/docs/assets/factbag.png" alt="An open canvas bag" align="right" width="180" />
8
+
9
+ Turn documents into evidence and conclusions with [Jev](https://typesafe.ai/) and TypeScript.
10
+
11
+ Factbag uses TypeSafe AI’s Jev model to read passages and assess specific claims.
12
+ Those assessments become evidence in a knowledge base, alongside facts from your
13
+ application. You write the rules that connect them and query what follows.
14
+
15
+ Each piece of documentary evidence points back to its source passage. Conclusions
16
+ can be **true**, **false**, **conflicted**, or **unknown**. When documents disagree,
17
+ you can inspect the conflicting evidence, correct a particular fact, and query
18
+ the rules again.
19
+
20
+ > **Important:** Factbag is an early alpha release and still pretty rough - APIs will definitely change.
21
+
22
+ ## How it works
23
+
24
+ 1. **Define the claims** you want Jev to assess, with descriptions of what counts
25
+ as evidence.
26
+ 2. **Add documents** to a corpus and choose which predicates Jev may assert or deny.
27
+ 3. **Execute classification jobs.** Jev assesses whether each passage supports,
28
+ contradicts, or is irrelevant to each candidate claim.
29
+ 4. **Query the result.** Factbag combines admitted evidence with your existing
30
+ facts and rules, retaining the sources behind the conclusion.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ npm install factbag@alpha @factbag/jev@alpha @typesafe-ai/sdk@^0.7.0
36
+ ```
37
+
38
+ `@factbag/jev` is released with `factbag` and needs the same version. To use
39
+ Cloudflare's Clef models on Workers AI instead, install `@factbag/clef@alpha`, which
40
+ does not need the TypeSafe SDK. To let an OpenAI-compatible model read and write a
41
+ knowledge base through tool calls, install `@factbag/openai@alpha`.
42
+
43
+ ESM only. Requires Node 22.18+ on the 22.x line, or Node 24 or 26.
44
+
45
+ Set your TypeSafe API key in the environment:
46
+
47
+ ```sh
48
+ export TYPESAFE_API_KEY="your-api-key"
49
+ ```
50
+
51
+ ## From a document to a conclusion
52
+
53
+ ### 1. Decide on your ruleset
54
+
55
+ Start with the criteria your application needs to apply. These might be your own
56
+ business rules or requirements set out in legislation or regulations.
57
+
58
+ Here, we're translating the individual-income route in the SEC's
59
+ [accredited-investor definition, 17 CFR § 230.501(a)(6)](<https://www.ecfr.gov/current/title-17/chapter-II/part-230/section-230.501#p-230.501(a)(6)>),
60
+ into three conditions:
61
+
62
+ - Individual income exceeded $200,000 in the first of the two most recent years.
63
+ - Individual income exceeded $200,000 in the second year.
64
+ - The person reasonably expects to reach the same income level in the current year.
65
+
66
+ ### 2. Define the claims
67
+
68
+ Declare the facts we'll combine, and describe the claims Jev will assess.
69
+
70
+ ```ts
71
+ import { enumeration, predicate } from "factbag/dsl";
72
+
73
+ class Person {
74
+ constructor(
75
+ readonly id: string,
76
+ readonly name: string,
77
+ ) {}
78
+ }
79
+
80
+ const hasIncomeOver200k = predicate(
81
+ "has_income_over_200k",
82
+ { person: Person, year: enumeration(2024, 2025) },
83
+ {
84
+ description:
85
+ "The person's total individual annual income exceeded USD 200,000 in the " +
86
+ "specified year. Joint household income and forecasts do not count.",
87
+ },
88
+ );
89
+
90
+ const expectsSameIncome = predicate(
91
+ "expects_same_income",
92
+ { person: Person },
93
+ {
94
+ description:
95
+ "The person reasonably expects their individual income in 2026 to remain " +
96
+ "at least at the level earned in each of 2024 and 2025.",
97
+ },
98
+ );
99
+
100
+ const isQualifiedInvestor = predicate("is_qualified_investor", {
101
+ person: Person,
102
+ });
103
+ const hasVerifiedIdentity = predicate("has_verified_identity", {
104
+ person: Person,
105
+ });
106
+ const isReadyForReview = predicate("is_ready_for_review", { person: Person });
107
+ ```
108
+
109
+ ### 3. Encode the ruleset
110
+
111
+ All three income conditions must hold for this route to qualification. Our
112
+ application also requires verified identity before sending an investor for review.
113
+
114
+ ```ts
115
+ import { and, variable } from "factbag/dsl";
116
+ import { KnowledgeBase } from "factbag/kb";
117
+
118
+ const person = variable("person");
119
+ const kb = new KnowledgeBase();
120
+
121
+ kb.rule(isQualifiedInvestor({ person })).if(
122
+ and(
123
+ hasIncomeOver200k({ person, year: 2024 }),
124
+ hasIncomeOver200k({ person, year: 2025 }),
125
+ expectsSameIncome({ person }),
126
+ ),
127
+ );
128
+
129
+ kb.rule(isReadyForReview({ person })).if(
130
+ and(isQualifiedInvestor({ person }), hasVerifiedIdentity({ person })),
131
+ );
132
+ ```
133
+
134
+ ### 4. Import a known fact and add documents
135
+
136
+ Identity verification comes directly from our onboarding system. Income evidence
137
+ goes into the corpus for Jev to assess.
138
+
139
+ ```ts
140
+ import { Corpus } from "factbag/corpus";
141
+
142
+ const alice = new Person("alice", "Alice");
143
+ kb.entities.add(alice);
144
+
145
+ kb.asserts(hasVerifiedIdentity({ person: alice }), {
146
+ source: "onboarding:alice",
147
+ });
148
+
149
+ const corpus = new Corpus();
150
+
151
+ // Tax returns provide evidence of past annual income.
152
+ corpus.add({ documentId: "alice-2024-federal-tax-return", text: "…" });
153
+ corpus.add({ documentId: "alice-2025-federal-tax-return", text: "…" });
154
+
155
+ // An employer letter supports the expectation of continued income.
156
+ corpus.add({
157
+ documentId: "alice-2026-employment-confirmation-letter",
158
+ text: "…",
159
+ });
160
+ ```
161
+
162
+ ### 5. Set Jev's permissions
163
+
164
+ The guard starts with no permissions. Permit Jev to assert or deny the two income
165
+ predicates.
166
+
167
+ ```ts
168
+ import { guard } from "factbag/kb";
169
+
170
+ const incomeEvidence = guard(kb)
171
+ .permit("assert", hasIncomeOver200k)
172
+ .permit("deny", hasIncomeOver200k)
173
+ .permit("assert", expectsSameIncome)
174
+ .permit("deny", expectsSameIncome);
175
+ ```
176
+
177
+ ### 6. Ask Jev to assess the income evidence
178
+
179
+ ```ts
180
+ import { classifier } from "@factbag/jev";
181
+
182
+ const run = classifier({ corpus, input: incomeEvidence });
183
+
184
+ for (const job of run) {
185
+ await job.execute(); // Makes a live Jev request.
186
+ }
187
+ ```
188
+
189
+ ### 7. Query the conclusion
190
+
191
+ The output knowledge base combines the imported fact and rules with the admitted
192
+ evidence.
193
+
194
+ ```ts
195
+ import { query } from "factbag/dsl";
196
+
197
+ const qualification = query(run.kb, isQualifiedInvestor({ person: alice }));
198
+ const reviewReadiness = query(run.kb, isReadyForReview({ person: alice }));
199
+
200
+ // Both are "true" if Jev admits supporting evidence for all three income claims.
201
+ qualification.value; // "true"
202
+ reviewReadiness.value; // "true"
203
+ ```
204
+
205
+ Results can be `true`, `false`, `conflicted`, or
206
+ `unknown`, depending on the evidence admitted.
207
+
208
+ ### 8. Inspect the proof tree
209
+
210
+ Use `explainJudgment()` to follow a conclusion through its rule to the supporting
211
+ evidence and source passages.
212
+
213
+ ```ts
214
+ import { explainJudgment } from "factbag/explain";
215
+
216
+ const proof = explainJudgment(qualification);
217
+ JSON.stringify(proof, null, 2); // JSON shown below.
218
+ ```
219
+
220
+ An abbreviated example with supporting evidence for all three claims:
221
+
222
+ ```json
223
+ {
224
+ "claim": "is_qualified_investor(alice) = true",
225
+ "because": "is_qualified_investor(person) if and(...)",
226
+ "given": [
227
+ {
228
+ "claim": "has_income_over_200k(alice, 2024) = true",
229
+ "because": {
230
+ "source": "alice-2024-federal-tax-return#0-1"
231
+ }
232
+ },
233
+ {
234
+ "claim": "has_income_over_200k(alice, 2025) = true",
235
+ "because": {
236
+ "source": "alice-2025-federal-tax-return#0-1"
237
+ }
238
+ },
239
+ {
240
+ "claim": "expects_same_income(alice) = true",
241
+ "because": {
242
+ "source": "alice-2026-employment-confirmation-letter#0-1"
243
+ }
244
+ }
245
+ ]
246
+ }
247
+ ```
248
+
249
+ Source IDs identify document passages; their ranges depend on the document text.
250
+ Use `corpus.resolve(source)` to retrieve the passage behind a piece of evidence.
251
+
252
+ ```ts
253
+ const source = "alice-2024-federal-tax-return#0-1";
254
+ corpus.resolve(source); // Object shown below.
255
+ ```
256
+
257
+ The result includes the exact passage and its containing document:
258
+
259
+ ```json
260
+ {
261
+ "chunk": {
262
+ "id": "alice-2024-federal-tax-return#0-1",
263
+ "documentId": "alice-2024-federal-tax-return",
264
+ "range": { "start": 0, "end": 1 },
265
+ "text": "…"
266
+ },
267
+ "document": {
268
+ "documentId": "alice-2024-federal-tax-return",
269
+ "text": "…",
270
+ "metadata": {}
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### 9. Review conflicting evidence (optional)
276
+
277
+ Suppose an original tax return puts Alice below the income threshold, but an
278
+ amended return puts her above it. Jev admits both accounts, leaving qualification
279
+ `conflicted`. In this example, the other income conditions have supporting evidence.
280
+
281
+ You could pass the conflicting evidence to a human for review, or ask Jev to
282
+ compare the sources using the `resolver`.
283
+
284
+ Evidence judged superseded is retracted from the resolver's output
285
+ knowledge base.
286
+
287
+ ```ts
288
+ import { resolver } from "@factbag/jev";
289
+
290
+ query(run.kb, isQualifiedInvestor({ person: alice })).value; // "conflicted"
291
+
292
+ const review = resolver(corpus, run.kb);
293
+
294
+ for (const job of review) {
295
+ await job.execute();
296
+ }
297
+
298
+ // If the resolver retracts the superseded return's denial:
299
+ query(review.kb, isQualifiedInvestor({ person: alice })).value; // "true"
300
+ ```
301
+
302
+ ## Go further
303
+
304
+ - [Introducing Factbag](https://daniel.du-pless.is/introducing-factbag): how and why
305
+ to use it.
306
+ - [Jev and the Art of Truth Maintenance](https://daniel.du-pless.is/jev-and-the-art-of-truth-maintenance):
307
+ the thinking behind the project.
308
+
309
+ Apache-2.0 licensed.