pagegraph 0.12.0 → 0.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.
- package/README.md +325 -66
- package/dist/anthropic-messages-TqKpKmfW.js +1067 -0
- package/dist/anthropic-messages-TqKpKmfW.js.map +1 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +5 -5
- package/dist/audit.js.map +1 -1
- package/dist/azure-openai-responses-DJjBLwoI.js +231 -0
- package/dist/azure-openai-responses-DJjBLwoI.js.map +1 -0
- package/dist/build-only/audit.js +1 -1
- package/dist/build-only/claims.d.ts +1 -0
- package/dist/build-only/claims.js +6 -0
- package/dist/build-only/claims.js.map +1 -0
- package/dist/build-only/config.js +1 -1
- package/dist/build-only/oxlint.d.ts +1 -0
- package/dist/build-only/oxlint.js +6 -0
- package/dist/build-only/oxlint.js.map +1 -0
- package/dist/build-only/tanstack-start.js +1 -1
- package/dist/build-only/vite.js +1 -1
- package/dist/{checks-uZk5Em9A.js → checks-BS-aDXB_.js} +2 -2
- package/dist/checks-BS-aDXB_.js.map +1 -0
- package/dist/claims-Bch7Lxlo.d.ts +92 -0
- package/dist/claims-Bv7voeEV.js +431 -0
- package/dist/claims-Bv7voeEV.js.map +1 -0
- package/dist/claims.d.ts +94 -0
- package/dist/claims.js +2 -0
- package/dist/cli.js +17607 -43387
- package/dist/cli.js.map +1 -1
- package/dist/cloudflare-workers-ai-system-one-f3ZrvNYZ.js +41 -0
- package/dist/cloudflare-workers-ai-system-one-f3ZrvNYZ.js.map +1 -0
- package/dist/config.d.ts +9 -29
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/{content-kcEnQdfi.js → content-CHd0X4s6.js} +2 -2
- package/dist/{content-kcEnQdfi.js.map → content-CHd0X4s6.js.map} +1 -1
- package/dist/{content-B1cPtL8b.d.ts → content-DgNr7apS.d.ts} +4 -5
- package/dist/declare-D5o7hwmv.js.map +1 -1
- package/dist/diagnostics-Wt5faNDM.js +108 -0
- package/dist/diagnostics-Wt5faNDM.js.map +1 -0
- package/dist/dist-B6zg_Z3Z.js +4794 -0
- package/dist/dist-B6zg_Z3Z.js.map +1 -0
- package/dist/document-CztEu3Vd.d.ts +109 -0
- package/dist/document-Dvemcv7V.js +379 -0
- package/dist/document-Dvemcv7V.js.map +1 -0
- package/dist/env-api-keys-C-OA0yM-.js +124 -0
- package/dist/env-api-keys-C-OA0yM-.js.map +1 -0
- package/dist/error-body-CyiejW4b.js +103 -0
- package/dist/error-body-CyiejW4b.js.map +1 -0
- package/dist/facts-1OKclPOk.js +115 -0
- package/dist/facts-1OKclPOk.js.map +1 -0
- package/dist/github-copilot-headers-CEpMOpQF.js +24 -0
- package/dist/github-copilot-headers-CEpMOpQF.js.map +1 -0
- package/dist/google-generative-ai-BxABKIKO.js +330 -0
- package/dist/google-generative-ai-BxABKIKO.js.map +1 -0
- package/dist/google-shared-0gpM2jFi.js +19471 -0
- package/dist/google-shared-0gpM2jFi.js.map +1 -0
- package/dist/google-vertex-D8Eb5cbu.js +376 -0
- package/dist/google-vertex-D8Eb5cbu.js.map +1 -0
- package/dist/{declare-DVgdEdOL.d.ts → graph-D1PIndsC.d.ts} +117 -6
- package/dist/{graph-Bcsourlv.js → graph-DMNOLXLu.js} +23 -13
- package/dist/graph-DMNOLXLu.js.map +1 -0
- package/dist/hash-Dp7EzZUo.js +18 -0
- package/dist/hash-Dp7EzZUo.js.map +1 -0
- package/dist/headers-BRNMVpzu.js +19 -0
- package/dist/headers-BRNMVpzu.js.map +1 -0
- package/dist/index-C5w9ripz.d.ts +204 -0
- package/dist/index.d.ts +6 -160
- package/dist/index.js +37 -5
- package/dist/index.js.map +1 -1
- package/dist/inline-BbS93GAT.js +682 -0
- package/dist/inline-BbS93GAT.js.map +1 -0
- package/dist/json-parse-C10znB7l.js +383 -0
- package/dist/json-parse-C10znB7l.js.map +1 -0
- package/dist/llms-Dhr_5u2O.js +19 -0
- package/dist/llms-Dhr_5u2O.js.map +1 -0
- package/dist/markdown-C6dRE9zD.js +168 -0
- package/dist/markdown-C6dRE9zD.js.map +1 -0
- package/dist/markdown-path-lJB9BAVZ.js +6 -0
- package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
- package/dist/{message-DT_CjUdt.js → message-CuBjgZ74.js} +2 -2
- package/dist/{message-DT_CjUdt.js.map → message-CuBjgZ74.js.map} +1 -1
- package/dist/mistral-conversations-CvaqBWjR.js +690 -0
- package/dist/mistral-conversations-CvaqBWjR.js.map +1 -0
- package/dist/models-DcT5qOEL.js +1203 -0
- package/dist/models-DcT5qOEL.js.map +1 -0
- package/dist/node-Ccvru9GE.js +26 -0
- package/dist/node-Ccvru9GE.js.map +1 -0
- package/dist/node-MEosBOF2.js +2403 -0
- package/dist/node-MEosBOF2.js.map +1 -0
- package/dist/openai-BCBEd2eO.js +17350 -0
- package/dist/openai-BCBEd2eO.js.map +1 -0
- package/dist/openai-codex-responses-vJqxeeRC.js +1268 -0
- package/dist/openai-codex-responses-vJqxeeRC.js.map +1 -0
- package/dist/openai-completions-Cb_fRAbI.js +989 -0
- package/dist/openai-completions-Cb_fRAbI.js.map +1 -0
- package/dist/openai-prompt-cache-Bc6qNWt0.js +10 -0
- package/dist/openai-prompt-cache-Bc6qNWt0.js.map +1 -0
- package/dist/openai-responses-sDuh82G5.js +268 -0
- package/dist/openai-responses-sDuh82G5.js.map +1 -0
- package/dist/openai-responses-shared-mhuo1Jov.js +599 -0
- package/dist/openai-responses-shared-mhuo1Jov.js.map +1 -0
- package/dist/openrouter-images-CRmBkzpX.js +127 -0
- package/dist/openrouter-images-CRmBkzpX.js.map +1 -0
- package/dist/oxlint.d.ts +58 -0
- package/dist/oxlint.js +202 -0
- package/dist/oxlint.js.map +1 -0
- package/dist/page-heads-BfjWQvau.d.ts +108 -0
- package/dist/{page-heads-6y-CARRQ.js → page-heads-BgbaIjj9.js} +3 -3
- package/dist/page-heads-BgbaIjj9.js.map +1 -0
- package/dist/pi-Bvw6J0gh.js +56795 -0
- package/dist/pi-Bvw6J0gh.js.map +1 -0
- package/dist/pi-messages-CejiVYvO.js +308 -0
- package/dist/pi-messages-CejiVYvO.js.map +1 -0
- package/dist/{projections-DeEFe7XT.d.ts → projections-Bk-qOOsR.d.ts} +2 -2
- package/dist/provider-env-t38GK5FF.js +39 -0
- package/dist/provider-env-t38GK5FF.js.map +1 -0
- package/dist/provider-retry-DzKoh1X7.js +80 -0
- package/dist/provider-retry-DzKoh1X7.js.map +1 -0
- package/dist/react.d.ts +3 -2
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/rolldown-runtime-B-lAHAz2.js +37 -0
- package/dist/sanitize-unicode-Ds14l9Om.js +28 -0
- package/dist/sanitize-unicode-Ds14l9Om.js.map +1 -0
- package/dist/sdk-C9_wiO6w.js +12503 -0
- package/dist/sdk-C9_wiO6w.js.map +1 -0
- package/dist/state-BuJP_3Lr.js +28102 -0
- package/dist/state-BuJP_3Lr.js.map +1 -0
- package/dist/system-one-shared-Dl10ZF7Z.js +167 -0
- package/dist/system-one-shared-Dl10ZF7Z.js.map +1 -0
- package/dist/tanstack-start/index.d.ts +29 -13
- package/dist/tanstack-start/index.js +391 -42
- package/dist/tanstack-start/index.js.map +1 -1
- package/dist/tanstack-start/markdown.d.ts +12 -0
- package/dist/tanstack-start/markdown.js +149 -0
- package/dist/tanstack-start/markdown.js.map +1 -0
- package/dist/tanstack-start/prerender-worker.d.ts +2 -0
- package/dist/tanstack-start/prerender-worker.js +7 -0
- package/dist/tanstack-start/prerender-worker.js.map +1 -0
- package/dist/tanstack-start/react.d.ts +7 -0
- package/dist/tanstack-start/react.js +12 -0
- package/dist/tanstack-start/react.js.map +1 -0
- package/dist/tanstack-start/server.d.ts +9 -3
- package/dist/tanstack-start/server.js +26 -2
- package/dist/tanstack-start/server.js.map +1 -1
- package/dist/transform-messages-BLJ8EfK6.js +468 -0
- package/dist/transform-messages-BLJ8EfK6.js.map +1 -0
- package/dist/typesafe-system-one-CwEWMV8P.js +25 -0
- package/dist/typesafe-system-one-CwEWMV8P.js.map +1 -0
- package/dist/{vite-graph-loader-01p1WxEq.js → vite-graph-loader-CsGiYNWE.js} +6 -5
- package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
- package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
- package/package.json +47 -10
- package/dist/checks-uZk5Em9A.js.map +0 -1
- package/dist/graph-Bcsourlv.js.map +0 -1
- package/dist/graph-BxCY90vq.d.ts +0 -104
- package/dist/graph-CBIY71w6.d.ts +0 -57
- package/dist/page-heads-6y-CARRQ.js.map +0 -1
- package/dist/page-heads-DNkjMUEN.d.ts +0 -21
- package/dist/vite-graph-loader-01p1WxEq.js.map +0 -1
- package/dist/vite-graph-loader-Btgjz5RH.d.ts +0 -56
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { a as globToRegExp } from "./checks-BS-aDXB_.js";
|
|
2
|
+
import { i as HEAD_SECTION_ID, o as hashDocument } from "./inline-BbS93GAT.js";
|
|
3
|
+
import { r as sectionMarkdown } from "./markdown-C6dRE9zD.js";
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import * as Data from "effect/Data";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Schema from "effect/Schema";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { DecisionModel } from "effect/ai";
|
|
11
|
+
//#region src/decide/answers.ts
|
|
12
|
+
/**
|
|
13
|
+
* Answer identity and validation shared by every decision family: how an input
|
|
14
|
+
* and a definition hash into a cache key, and whether stored answers have the
|
|
15
|
+
* shape the provider path would have validated.
|
|
16
|
+
*/
|
|
17
|
+
const isUnitInterval = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
18
|
+
const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
|
+
/** A probability distribution over exactly `labels`: unit-range, summing to 1. */
|
|
20
|
+
const isDistribution = (labels, value) => {
|
|
21
|
+
if (!isPlainObject(value) || Object.keys(value).length !== labels.length) return false;
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (const label of labels) {
|
|
24
|
+
const probability = value[label];
|
|
25
|
+
if (!isUnitInterval(probability)) return false;
|
|
26
|
+
total += probability;
|
|
27
|
+
}
|
|
28
|
+
return Math.abs(total - 1) <= 1e-6;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Whether cached answers have the shape `DecisionModel` would have validated —
|
|
32
|
+
* the same labels, ranges, and distributions. A cache file is only trusted when
|
|
33
|
+
* it round-trips through this check, so a partially malformed entry cannot
|
|
34
|
+
* produce a confident verdict.
|
|
35
|
+
*/
|
|
36
|
+
const validCachedAnswers = (decisions, answers) => {
|
|
37
|
+
if (!isPlainObject(answers)) return false;
|
|
38
|
+
for (const key of Object.keys(decisions)) {
|
|
39
|
+
const answer = answers[key];
|
|
40
|
+
if (!isPlainObject(answer)) return false;
|
|
41
|
+
const decision = decisions[key];
|
|
42
|
+
switch (decision._tag) {
|
|
43
|
+
case "Probability":
|
|
44
|
+
if (!isUnitInterval(answer.probability)) return false;
|
|
45
|
+
break;
|
|
46
|
+
case "Classify":
|
|
47
|
+
if (typeof answer.label !== "string" || !Object.hasOwn(decision.criteria, answer.label) || !isDistribution(Object.keys(decision.criteria), answer.probabilities)) return false;
|
|
48
|
+
break;
|
|
49
|
+
case "Rate": if (typeof answer.rating !== "number" || !Number.isFinite(answer.rating) || answer.rating < 0 || answer.rating > decision.criteria.length - 1 || typeof answer.label !== "string" || !decision.criteria.includes(answer.label) || !isDistribution(decision.criteria, answer.probabilities)) return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
};
|
|
54
|
+
/** sha256 of the canonical input JSON — deterministic for a schema-decoded input. */
|
|
55
|
+
const inputHash = (input) => createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
56
|
+
/** JSON with object keys sorted at every level, so key order never changes a hash. */
|
|
57
|
+
const canonicalJson = (value) => JSON.stringify(value, (_key, entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) : entry);
|
|
58
|
+
/**
|
|
59
|
+
* sha256 of what the model is asked: each decision's kind, instructions, and
|
|
60
|
+
* criteria. Editing a prompt or a label therefore invalidates its answers.
|
|
61
|
+
*/
|
|
62
|
+
const definitionFingerprint = (decisions) => createHash("sha256").update(canonicalJson(Object.fromEntries(Object.entries(decisions).map(([name, decision]) => [name, {
|
|
63
|
+
kind: decision._tag,
|
|
64
|
+
instructions: decision.instructions,
|
|
65
|
+
criteria: decision.criteria ?? null
|
|
66
|
+
}])))).digest("hex");
|
|
67
|
+
/**
|
|
68
|
+
* Cache identity. Answers depend on the family, the model, the question asked,
|
|
69
|
+
* and the input; the answer-schema version guards shape changes.
|
|
70
|
+
*/
|
|
71
|
+
const cacheKey = (parts) => createHash("sha256").update([
|
|
72
|
+
parts.family,
|
|
73
|
+
parts.model,
|
|
74
|
+
`v1`,
|
|
75
|
+
definitionFingerprint(parts.decisions),
|
|
76
|
+
parts.inputHash
|
|
77
|
+
].join("\0")).digest("hex");
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/decide/cache.ts
|
|
80
|
+
/**
|
|
81
|
+
* Answer cache keyed by {@link cacheKey}: one JSON file per key in a directory.
|
|
82
|
+
*
|
|
83
|
+
* Only model answers are cached; verdicts are recomputed from answers and the
|
|
84
|
+
* current threshold, so changing a threshold never serves a stale verdict.
|
|
85
|
+
*
|
|
86
|
+
* Two policies:
|
|
87
|
+
* - `scratch`: a working cache for agentic runs. A missing, unreadable, or
|
|
88
|
+
* malformed file is a miss, and the provider answers again.
|
|
89
|
+
* - `committed`: answers reviewed and committed with the code they judge, read
|
|
90
|
+
* by gates that replay without a model. Only absence is a miss; an unreadable
|
|
91
|
+
* or malformed file fails, so a corrupted answer can never pass silently.
|
|
92
|
+
* Writes are atomic, so an interrupted refresh cannot leave a partial file.
|
|
93
|
+
*/
|
|
94
|
+
/** A committed answer that exists but cannot be trusted. */
|
|
95
|
+
var DecisionCacheInvalid = class extends Data.TaggedError("DecisionCacheInvalid") {};
|
|
96
|
+
const answerFile = (cache, key) => join(cache.directory, `${key}.json`);
|
|
97
|
+
/**
|
|
98
|
+
* Cached answers for `key` when they match `decisions`, `undefined` on a miss.
|
|
99
|
+
* Under `committed`, an unreadable or mismatched file fails instead of missing.
|
|
100
|
+
*/
|
|
101
|
+
const readAnswers = (cache, key, decisions) => Effect.gen(function* () {
|
|
102
|
+
const file = answerFile(cache, key);
|
|
103
|
+
if (!existsSync(file)) return void 0;
|
|
104
|
+
const invalid = (reason) => cache.policy === "scratch" ? Effect.succeed(void 0) : Effect.fail(new DecisionCacheInvalid({
|
|
105
|
+
file,
|
|
106
|
+
message: `Committed decision answers in ${file} ${reason}; re-ask them and commit the result.`
|
|
107
|
+
}));
|
|
108
|
+
const parsed = yield* Effect.try({
|
|
109
|
+
try: () => JSON.parse(readFileSync(file, "utf8")),
|
|
110
|
+
catch: () => file
|
|
111
|
+
}).pipe(Effect.option);
|
|
112
|
+
if (parsed._tag === "None") return yield* invalid("are not readable JSON");
|
|
113
|
+
if (!validCachedAnswers(decisions, parsed.value)) return yield* invalid("do not match the decision definition");
|
|
114
|
+
return parsed.value;
|
|
115
|
+
});
|
|
116
|
+
/** Persist answers for `key`, creating the directory on first write. */
|
|
117
|
+
const writeAnswers = (cache, key, answers) => Effect.sync(() => {
|
|
118
|
+
mkdirSync(cache.directory, { recursive: true });
|
|
119
|
+
const contents = `${JSON.stringify(answers, null, 2)}\n`;
|
|
120
|
+
if (cache.policy === "scratch") {
|
|
121
|
+
writeFileSync(answerFile(cache, key), contents, "utf8");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const staging = mkdtempSync(join(cache.directory, ".answer-"));
|
|
125
|
+
try {
|
|
126
|
+
const staged = join(staging, "answer.json");
|
|
127
|
+
writeFileSync(staged, contents, "utf8");
|
|
128
|
+
renameSync(staged, answerFile(cache, key));
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(staging, {
|
|
131
|
+
recursive: true,
|
|
132
|
+
force: true
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/decide/families/claims.ts
|
|
138
|
+
const FactDefinition = Schema.Struct({
|
|
139
|
+
kind: Schema.Literals([
|
|
140
|
+
"number",
|
|
141
|
+
"money",
|
|
142
|
+
"list",
|
|
143
|
+
"text"
|
|
144
|
+
]),
|
|
145
|
+
value: Schema.Json,
|
|
146
|
+
text: Schema.String,
|
|
147
|
+
items: Schema.optionalKey(Schema.Array(Schema.String))
|
|
148
|
+
});
|
|
149
|
+
/** Authored prose, code-owned facts, and evidence supplied to adopter rules. */
|
|
150
|
+
const claimsInput = Schema.Struct({
|
|
151
|
+
path: Schema.String,
|
|
152
|
+
evidence: Schema.optionalKey(Schema.String),
|
|
153
|
+
section: Schema.Struct({
|
|
154
|
+
id: Schema.String,
|
|
155
|
+
kind: Schema.String,
|
|
156
|
+
markdown: Schema.String,
|
|
157
|
+
messages: Schema.Array(Schema.Struct({
|
|
158
|
+
hash: Schema.String,
|
|
159
|
+
text: Schema.String,
|
|
160
|
+
facts: Schema.Array(Schema.String)
|
|
161
|
+
}))
|
|
162
|
+
}),
|
|
163
|
+
facts: Schema.Record(Schema.String, FactDefinition),
|
|
164
|
+
context: Schema.optionalKey(Schema.String)
|
|
165
|
+
});
|
|
166
|
+
const ClaimsAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));
|
|
167
|
+
/** Claims use a single upper cutoff; probabilities below it pass without a review band. */
|
|
168
|
+
function claimsFamily(rules) {
|
|
169
|
+
if (Object.keys(rules.decisions).length === 0) throw new Error("Claims need at least one decision rule");
|
|
170
|
+
if (Object.values(rules.decisions).some((rule) => rule._tag !== "Probability")) throw new Error("Claims rules must be probability decisions");
|
|
171
|
+
return {
|
|
172
|
+
name: "claims",
|
|
173
|
+
input: claimsInput,
|
|
174
|
+
definitionFor: () => rules,
|
|
175
|
+
inputRef: (input) => `${input.path} › ${input.section.id}`,
|
|
176
|
+
evaluate: (_input, answers, cutoff) => Object.values(Schema.decodeUnknownSync(ClaimsAnswers)(answers)).some((answer) => answer.probability >= cutoff) ? "violation" : "pass"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Heads describe only pages outside capture; duplicate ownership fails before evaluation. */
|
|
180
|
+
function resolveClaimsDocuments(documents, heads, excludeHeads = []) {
|
|
181
|
+
const paths = /* @__PURE__ */ new Set();
|
|
182
|
+
for (const document of documents) {
|
|
183
|
+
if (paths.has(document.path)) throw new Error(`Duplicate claims page path "${document.path}"`);
|
|
184
|
+
paths.add(document.path);
|
|
185
|
+
}
|
|
186
|
+
const seenHeads = /* @__PURE__ */ new Set();
|
|
187
|
+
const excludes = excludeHeads.map(globToRegExp);
|
|
188
|
+
const extra = [];
|
|
189
|
+
for (const head of heads) {
|
|
190
|
+
if (seenHeads.has(head.path)) throw new Error(`Duplicate claims head path "${head.path}"`);
|
|
191
|
+
seenHeads.add(head.path);
|
|
192
|
+
if (paths.has(head.path)) throw new Error(`Duplicate claims page path "${head.path}"`);
|
|
193
|
+
if (excludes.some((pattern) => pattern.test(head.path))) continue;
|
|
194
|
+
const document = {
|
|
195
|
+
...head,
|
|
196
|
+
sections: [],
|
|
197
|
+
messages: [],
|
|
198
|
+
facts: []
|
|
199
|
+
};
|
|
200
|
+
extra.push({
|
|
201
|
+
...document,
|
|
202
|
+
hash: hashDocument(document)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return [...documents, ...extra];
|
|
206
|
+
}
|
|
207
|
+
function sectionMessages(section) {
|
|
208
|
+
return [
|
|
209
|
+
...section.title === void 0 ? [] : [section.title],
|
|
210
|
+
...section.body.flatMap((entry) => entry.type === "message" ? [entry.message] : []),
|
|
211
|
+
...section.items.flatMap((item) => [
|
|
212
|
+
...item.title === void 0 ? [] : [item.title],
|
|
213
|
+
...item.body.flatMap((entry) => entry.type === "message" ? [entry.message] : []),
|
|
214
|
+
...item.cells
|
|
215
|
+
])
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
/** Source locations do not enter model input or answer identity. */
|
|
219
|
+
function claimsInputs(documents, heads, facts, options = {}) {
|
|
220
|
+
const excludes = (options.excludeHeads ?? []).map(globToRegExp);
|
|
221
|
+
return resolveClaimsDocuments(documents, heads, options.excludeHeads).flatMap((document) => {
|
|
222
|
+
return [...document.sections, ...excludes.some((pattern) => pattern.test(document.path)) ? [] : [{
|
|
223
|
+
id: HEAD_SECTION_ID,
|
|
224
|
+
kind: "head",
|
|
225
|
+
claimsMarkdown: `${document.title}\n\n${document.description}`,
|
|
226
|
+
body: [],
|
|
227
|
+
items: [],
|
|
228
|
+
source: document.path,
|
|
229
|
+
audience: "all"
|
|
230
|
+
}]].map((section) => {
|
|
231
|
+
const evidence = document.sections.filter((entry) => entry.evidence === true && entry !== section).map((entry) => entry.claimsMarkdown ?? sectionMarkdown(entry, { includeHumans: true })).join("\n\n");
|
|
232
|
+
return Schema.decodeUnknownSync(claimsInput)({
|
|
233
|
+
path: document.path,
|
|
234
|
+
...evidence === "" ? {} : { evidence },
|
|
235
|
+
section: {
|
|
236
|
+
id: section.id,
|
|
237
|
+
kind: section.kind,
|
|
238
|
+
markdown: section.claimsMarkdown ?? sectionMarkdown(section, { includeHumans: true }),
|
|
239
|
+
messages: sectionMessages(section).map(({ hash, text, facts }) => ({
|
|
240
|
+
hash,
|
|
241
|
+
text,
|
|
242
|
+
facts
|
|
243
|
+
}))
|
|
244
|
+
},
|
|
245
|
+
facts,
|
|
246
|
+
...options.context === void 0 ? {} : { context: options.context }
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/decide/run.ts
|
|
253
|
+
/**
|
|
254
|
+
* The decision runner. A family supplies its input schema, a definition built
|
|
255
|
+
* from the input (families with dynamic labels build one per input), a way to
|
|
256
|
+
* reference the input in a report, and a pure `evaluate` that maps the model's
|
|
257
|
+
* answers to a verdict. The runner hashes, reads and writes the answer cache,
|
|
258
|
+
* dispatches with bounded concurrency, and buckets confident answers apart from
|
|
259
|
+
* `review`. `runDecisions` asks the model on a miss; `replayDecisions` answers
|
|
260
|
+
* from a committed cache alone and fails on a miss.
|
|
261
|
+
*
|
|
262
|
+
* Build and CLI entries reach Effect and the provider through this module;
|
|
263
|
+
* runtime entries remain Effect-free and Node-free.
|
|
264
|
+
*/
|
|
265
|
+
/** Replay found no committed answer for an input. */
|
|
266
|
+
var DecisionCacheMiss = class extends Data.TaggedError("DecisionCacheMiss") {};
|
|
267
|
+
/** Group records into resolved/review buckets and count each verdict. */
|
|
268
|
+
const buildDecisionReport = (options) => {
|
|
269
|
+
const verdicts = {};
|
|
270
|
+
const resolved = [];
|
|
271
|
+
const review = [];
|
|
272
|
+
for (const record of options.records) {
|
|
273
|
+
verdicts[record.verdict] = (verdicts[record.verdict] ?? 0) + 1;
|
|
274
|
+
if (record.review) review.push(record);
|
|
275
|
+
else resolved.push(record);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
kind: "decide",
|
|
279
|
+
schemaVersion: 1,
|
|
280
|
+
family: options.family,
|
|
281
|
+
model: options.model,
|
|
282
|
+
threshold: options.threshold,
|
|
283
|
+
counts: {
|
|
284
|
+
inputs: options.records.length,
|
|
285
|
+
resolved: resolved.length,
|
|
286
|
+
review: review.length
|
|
287
|
+
},
|
|
288
|
+
verdicts,
|
|
289
|
+
resolved,
|
|
290
|
+
review
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Answer each input from the cache, or through `ask` on a miss, and bucket the
|
|
295
|
+
* results. Inputs run with bounded concurrency.
|
|
296
|
+
*/
|
|
297
|
+
const decideAll = (options, ask, reuseCache) => Effect.gen(function* () {
|
|
298
|
+
const records = yield* Effect.forEach(options.inputs, (input, index) => Effect.gen(function* () {
|
|
299
|
+
const definition = options.family.definitionFor(input);
|
|
300
|
+
const hash = inputHash(input);
|
|
301
|
+
const key = cacheKey({
|
|
302
|
+
family: options.family.name,
|
|
303
|
+
model: options.model,
|
|
304
|
+
decisions: definition.decisions,
|
|
305
|
+
inputHash: hash
|
|
306
|
+
});
|
|
307
|
+
const cached = options.cache === void 0 || !reuseCache ? void 0 : yield* readAnswers(options.cache, key, definition.decisions);
|
|
308
|
+
const answered = cached === void 0 ? yield* ask(input, key) : { answers: cached };
|
|
309
|
+
const verdict = options.family.evaluate(input, answered.answers, options.threshold);
|
|
310
|
+
return {
|
|
311
|
+
decisionId: `${options.family.name}:${index}`,
|
|
312
|
+
schemaVersion: 1,
|
|
313
|
+
family: options.family.name,
|
|
314
|
+
model: options.model,
|
|
315
|
+
threshold: options.threshold,
|
|
316
|
+
inputHash: hash,
|
|
317
|
+
inputRef: options.family.inputRef(input),
|
|
318
|
+
verdict,
|
|
319
|
+
review: verdict === "review",
|
|
320
|
+
answers: answered.answers,
|
|
321
|
+
...answered.usage === void 0 ? {} : { usage: answered.usage }
|
|
322
|
+
};
|
|
323
|
+
}), { concurrency: options.concurrency ?? 4 });
|
|
324
|
+
return buildDecisionReport({
|
|
325
|
+
family: options.family.name,
|
|
326
|
+
model: options.model,
|
|
327
|
+
threshold: options.threshold,
|
|
328
|
+
records
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
/**
|
|
332
|
+
* Answer every input through the ambient `DecisionModel`. One provider call
|
|
333
|
+
* answers every decision for an input; a cached answer skips the provider unless
|
|
334
|
+
* `refresh` asks again. New answers are written to the cache.
|
|
335
|
+
*/
|
|
336
|
+
const runDecisions = (options) => decideAll(options, (input, key) => Effect.gen(function* () {
|
|
337
|
+
const response = yield* DecisionModel.decide(options.family.definitionFor(input), { input });
|
|
338
|
+
if (options.cache !== void 0) yield* writeAnswers(options.cache, key, response.answers);
|
|
339
|
+
return {
|
|
340
|
+
answers: response.answers,
|
|
341
|
+
usage: {
|
|
342
|
+
inputTokens: response.usage.inputTokens,
|
|
343
|
+
outputTokens: response.usage.outputTokens
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}), options.refresh !== true);
|
|
347
|
+
/**
|
|
348
|
+
* Answer every input from the cache alone. Needs no model or credentials, so a
|
|
349
|
+
* gate can run it offline; an input without an answer fails the replay.
|
|
350
|
+
*/
|
|
351
|
+
const replayDecisions = (options) => decideAll(options, (input, key) => Effect.fail(new DecisionCacheMiss({
|
|
352
|
+
family: options.family.name,
|
|
353
|
+
inputRef: options.family.inputRef(input),
|
|
354
|
+
key,
|
|
355
|
+
message: `No ${options.family.name} answer for ${options.family.inputRef(input)}; ask the model and commit the answers.`
|
|
356
|
+
})), true);
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/claims.ts
|
|
359
|
+
var ClaimsFailed = class extends Data.TaggedError("ClaimsFailed") {};
|
|
360
|
+
/** Cutoff affects verdicts alone; committed identity belongs to the decision runner. */
|
|
361
|
+
function claimsRunOptions(root, documents, heads, facts, options) {
|
|
362
|
+
const cutoff = options.cutoff === void 0 ? .8 : options.cutoff;
|
|
363
|
+
if (!Number.isFinite(cutoff) || cutoff < 0 || cutoff > 1) throw new Error("Claims cutoff must be between 0 and 1");
|
|
364
|
+
if (!options.model.trim()) throw new Error("Claims model is required");
|
|
365
|
+
return {
|
|
366
|
+
family: claimsFamily(options.rules),
|
|
367
|
+
inputs: claimsInputs(documents, heads, facts, options),
|
|
368
|
+
model: options.model,
|
|
369
|
+
threshold: cutoff,
|
|
370
|
+
cache: {
|
|
371
|
+
directory: resolve(root, ".pagegraph/decisions/claims"),
|
|
372
|
+
policy: "committed"
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const ProbabilityAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));
|
|
377
|
+
function claimsReport(batch, documents, heads, facts, options, mode) {
|
|
378
|
+
const inputs = claimsInputs(documents, heads, facts, options);
|
|
379
|
+
const sources = new Map(documents.map((document) => [document.path, document]));
|
|
380
|
+
const records = [...batch.resolved, ...batch.review];
|
|
381
|
+
const findings = records.flatMap((record) => {
|
|
382
|
+
const index = Number(record.decisionId.slice(7));
|
|
383
|
+
const input = inputs[index];
|
|
384
|
+
if (input === void 0) throw new Error(`Unknown claims decision ${record.decisionId}`);
|
|
385
|
+
return Object.entries(Schema.decodeUnknownSync(ProbabilityAnswers)(record.answers)).flatMap(([rule, answer]) => answer.probability >= batch.threshold ? [{
|
|
386
|
+
path: input.path,
|
|
387
|
+
section: input.section.id,
|
|
388
|
+
source: sources.get(input.path)?.sections.find((section) => section.id === input.section.id)?.source ?? input.path,
|
|
389
|
+
rule,
|
|
390
|
+
probability: answer.probability
|
|
391
|
+
}] : []);
|
|
392
|
+
});
|
|
393
|
+
const asked = records.filter((record) => record.usage !== void 0).length;
|
|
394
|
+
return {
|
|
395
|
+
version: 1,
|
|
396
|
+
mode,
|
|
397
|
+
pages: resolveClaimsDocuments(documents, heads, options.excludeHeads).length,
|
|
398
|
+
messages: documents.reduce((count, document) => count + document.messages.length, 0),
|
|
399
|
+
cached: records.length - asked,
|
|
400
|
+
asked,
|
|
401
|
+
cutoff: batch.threshold,
|
|
402
|
+
findings
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/** Ask only for absent answers unless refresh is requested, then persist committed answers. */
|
|
406
|
+
const runClaims = Effect.fn("Claims.run")(function* (root, documents, heads, facts, options, runOptions = {}) {
|
|
407
|
+
return claimsReport(yield* runDecisions({
|
|
408
|
+
...claimsRunOptions(root, documents, heads, facts, options),
|
|
409
|
+
...runOptions
|
|
410
|
+
}), documents, heads, facts, options, "ask");
|
|
411
|
+
});
|
|
412
|
+
/** Offline gates have no model requirement; absent answers and violations fail with author guidance. */
|
|
413
|
+
const replayClaims = Effect.fn("Claims.replay")(function* (root, documents, heads, facts, options) {
|
|
414
|
+
const report = claimsReport(yield* replayDecisions(claimsRunOptions(root, documents, heads, facts, options)).pipe(Effect.catchTag("DecisionCacheInvalid", (error) => Effect.fail(new DecisionCacheInvalid({
|
|
415
|
+
file: error.file,
|
|
416
|
+
message: `${error.message} Run pagegraph claims check --refresh and commit the answers.`
|
|
417
|
+
}))), Effect.catchTag("DecisionCacheMiss", (error) => Effect.fail(new DecisionCacheMiss({
|
|
418
|
+
...error,
|
|
419
|
+
message: `No claims answer for ${error.inputRef} (rules: ${Object.keys(options.rules.decisions).join(", ")}). Run pagegraph claims check and commit the answers.`
|
|
420
|
+
})))), documents, heads, facts, options, "replay");
|
|
421
|
+
if (report.findings.length > 0) return yield* new ClaimsFailed({
|
|
422
|
+
message: report.findings.map((finding) => `Claims violation at ${finding.path} › ${finding.section}: ${finding.rule} (${finding.probability.toFixed(2)}).`).join("\n") + "\nRun pagegraph claims check and commit the answers.",
|
|
423
|
+
findings: report.findings,
|
|
424
|
+
report
|
|
425
|
+
});
|
|
426
|
+
return report;
|
|
427
|
+
});
|
|
428
|
+
//#endregion
|
|
429
|
+
export { claimsFamily as a, resolveClaimsDocuments as c, runClaims as i, claimsRunOptions as n, claimsInput as o, replayClaims as r, claimsInputs as s, ClaimsFailed as t };
|
|
430
|
+
|
|
431
|
+
//# sourceMappingURL=claims-Bv7voeEV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims-Bv7voeEV.js","names":[],"sources":["../src/decide/answers.ts","../src/decide/cache.ts","../src/decide/families/claims.ts","../src/decide/run.ts","../src/claims.ts"],"sourcesContent":["/**\n * Answer identity and validation shared by every decision family: how an input\n * and a definition hash into a cache key, and whether stored answers have the\n * shape the provider path would have validated.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport type * as Decision from \"effect/ai/Decision\";\n\nconst isUnitInterval = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 1;\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** A probability distribution over exactly `labels`: unit-range, summing to 1. */\nconst isDistribution = (labels: ReadonlyArray<string>, value: unknown): boolean => {\n // Extra labels would be read by evaluators that scan the distribution, so\n // require the cached keys to match the declared labels exactly.\n if (!isPlainObject(value) || Object.keys(value).length !== labels.length) return false;\n let total = 0;\n for (const label of labels) {\n const probability = value[label];\n if (!isUnitInterval(probability)) return false;\n total += probability;\n }\n return Math.abs(total - 1) <= 1e-6;\n};\n\n/**\n * Whether cached answers have the shape `DecisionModel` would have validated —\n * the same labels, ranges, and distributions. A cache file is only trusted when\n * it round-trips through this check, so a partially malformed entry cannot\n * produce a confident verdict.\n */\nexport const validCachedAnswers = (\n decisions: Readonly<Record<string, Decision.Any>>,\n answers: unknown,\n): boolean => {\n if (!isPlainObject(answers)) return false;\n for (const key of Object.keys(decisions)) {\n const answer = answers[key];\n if (!isPlainObject(answer)) return false;\n const decision = decisions[key]!;\n switch (decision._tag) {\n case \"Probability\":\n if (!isUnitInterval(answer.probability)) return false;\n break;\n case \"Classify\":\n if (\n typeof answer.label !== \"string\" ||\n !Object.hasOwn(decision.criteria, answer.label) ||\n !isDistribution(Object.keys(decision.criteria), answer.probabilities)\n ) {\n return false;\n }\n break;\n case \"Rate\":\n if (\n typeof answer.rating !== \"number\" ||\n !Number.isFinite(answer.rating) ||\n answer.rating < 0 ||\n answer.rating > decision.criteria.length - 1 ||\n typeof answer.label !== \"string\" ||\n !decision.criteria.includes(answer.label) ||\n !isDistribution(decision.criteria, answer.probabilities)\n ) {\n return false;\n }\n break;\n }\n }\n return true;\n};\n\n/** sha256 of the canonical input JSON — deterministic for a schema-decoded input. */\nexport const inputHash = (input: unknown): string =>\n createHash(\"sha256\").update(JSON.stringify(input)).digest(\"hex\");\n\n/** Bump when a family's answer shape changes, so old cache files stop matching. */\nexport const DECISION_ANSWER_SCHEMA_VERSION = 1;\n\n/** JSON with object keys sorted at every level, so key order never changes a hash. */\nconst canonicalJson = (value: unknown): string =>\n JSON.stringify(value, (_key, entry: unknown) =>\n entry !== null && typeof entry === \"object\" && !Array.isArray(entry)\n ? Object.fromEntries(Object.entries(entry).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)))\n : entry,\n );\n\n/**\n * sha256 of what the model is asked: each decision's kind, instructions, and\n * criteria. Editing a prompt or a label therefore invalidates its answers.\n */\nexport const definitionFingerprint = (decisions: Readonly<Record<string, Decision.Any>>): string =>\n createHash(\"sha256\")\n .update(\n canonicalJson(\n Object.fromEntries(\n Object.entries(decisions).map(([name, decision]) => [\n name,\n { kind: decision._tag, instructions: decision.instructions, criteria: decision.criteria ?? null },\n ]),\n ),\n ),\n )\n .digest(\"hex\");\n\n/**\n * Cache identity. Answers depend on the family, the model, the question asked,\n * and the input; the answer-schema version guards shape changes.\n */\nexport const cacheKey = (parts: {\n readonly family: string;\n readonly model: string;\n readonly decisions: Readonly<Record<string, Decision.Any>>;\n readonly inputHash: string;\n}): string =>\n createHash(\"sha256\")\n .update(\n [\n parts.family,\n parts.model,\n `v${DECISION_ANSWER_SCHEMA_VERSION}`,\n definitionFingerprint(parts.decisions),\n parts.inputHash,\n ].join(\"\\u0000\"),\n )\n .digest(\"hex\");\n","/**\n * Answer cache keyed by {@link cacheKey}: one JSON file per key in a directory.\n *\n * Only model answers are cached; verdicts are recomputed from answers and the\n * current threshold, so changing a threshold never serves a stale verdict.\n *\n * Two policies:\n * - `scratch`: a working cache for agentic runs. A missing, unreadable, or\n * malformed file is a miss, and the provider answers again.\n * - `committed`: answers reviewed and committed with the code they judge, read\n * by gates that replay without a model. Only absence is a miss; an unreadable\n * or malformed file fails, so a corrupted answer can never pass silently.\n * Writes are atomic, so an interrupted refresh cannot leave a partial file.\n */\n\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { validCachedAnswers } from \"./answers\";\n\nexport type DecisionCachePolicy = \"scratch\" | \"committed\";\n\nexport interface DecisionCache {\n readonly directory: string;\n readonly policy: DecisionCachePolicy;\n}\n\n/** A committed answer that exists but cannot be trusted. */\nexport class DecisionCacheInvalid extends Data.TaggedError(\"DecisionCacheInvalid\")<{\n readonly file: string;\n readonly message: string;\n}> {}\n\nconst answerFile = (cache: DecisionCache, key: string): string => join(cache.directory, `${key}.json`);\n\n/**\n * Cached answers for `key` when they match `decisions`, `undefined` on a miss.\n * Under `committed`, an unreadable or mismatched file fails instead of missing.\n */\nexport const readAnswers = (\n cache: DecisionCache,\n key: string,\n decisions: Readonly<Record<string, Decision.Any>>,\n): Effect.Effect<unknown | undefined, DecisionCacheInvalid> =>\n Effect.gen(function* () {\n const file = answerFile(cache, key);\n if (!existsSync(file)) return undefined;\n const invalid = (reason: string) =>\n cache.policy === \"scratch\"\n ? Effect.succeed(undefined)\n : Effect.fail(\n new DecisionCacheInvalid({\n file,\n message: `Committed decision answers in ${file} ${reason}; re-ask them and commit the result.`,\n }),\n );\n const parsed = yield* Effect.try({\n try: (): unknown => JSON.parse(readFileSync(file, \"utf8\")),\n catch: () => file,\n }).pipe(Effect.option);\n if (parsed._tag === \"None\") return yield* invalid(\"are not readable JSON\");\n if (!validCachedAnswers(decisions, parsed.value)) return yield* invalid(\"do not match the decision definition\");\n return parsed.value;\n });\n\n/** Persist answers for `key`, creating the directory on first write. */\nexport const writeAnswers = (cache: DecisionCache, key: string, answers: unknown): Effect.Effect<void> =>\n Effect.sync(() => {\n mkdirSync(cache.directory, { recursive: true });\n const contents = `${JSON.stringify(answers, null, 2)}\\n`;\n if (cache.policy === \"scratch\") {\n writeFileSync(answerFile(cache, key), contents, \"utf8\");\n return;\n }\n // Stage beside the target so the rename stays on one filesystem.\n const staging = mkdtempSync(join(cache.directory, \".answer-\"));\n try {\n const staged = join(staging, \"answer.json\");\n writeFileSync(staged, contents, \"utf8\");\n renameSync(staged, answerFile(cache, key));\n } finally {\n rmSync(staging, { recursive: true, force: true });\n }\n });\n","import * as Schema from \"effect/Schema\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { globToRegExp } from \"../../core/checks\";\nimport type { PageHead } from \"../../core/page-heads\";\nimport { HEAD_SECTION_ID, hashDocument, type Message, type PageDocument, type SectionDocument } from \"../../markdown/document\";\nimport type { Facts } from \"../../markdown/facts\";\nimport { sectionMarkdown } from \"../../markdown/markdown\";\nimport type { DecisionFamily } from \"../run\";\n\nconst FactDefinition = Schema.Struct({\n kind: Schema.Literals([\"number\", \"money\", \"list\", \"text\"]),\n value: Schema.Json,\n text: Schema.String,\n items: Schema.optionalKey(Schema.Array(Schema.String)),\n});\n\n/** Authored prose, code-owned facts, and evidence supplied to adopter rules. */\nexport const claimsInput = Schema.Struct({\n path: Schema.String,\n evidence: Schema.optionalKey(Schema.String),\n section: Schema.Struct({\n id: Schema.String,\n kind: Schema.String,\n markdown: Schema.String,\n messages: Schema.Array(Schema.Struct({\n hash: Schema.String, text: Schema.String, facts: Schema.Array(Schema.String),\n })),\n }),\n facts: Schema.Record(Schema.String, FactDefinition),\n context: Schema.optionalKey(Schema.String),\n});\nexport type ClaimsInput = typeof claimsInput.Type;\nexport type ClaimsRules = Decision.Definition<typeof claimsInput, Record<string, Decision.Probability>>;\nexport interface ClaimsOptions {\n readonly rules: ClaimsRules;\n readonly model: string;\n readonly cutoff?: number;\n readonly context?: string;\n /** Exclude metadata by path glob; captured sections remain in the gate. */\n readonly excludeHeads?: ReadonlyArray<string>;\n}\nexport type ClaimsVerdict = \"pass\" | \"violation\";\nconst ClaimsAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\n\n/** Claims use a single upper cutoff; probabilities below it pass without a review band. */\nexport function claimsFamily(rules: ClaimsRules): DecisionFamily<ClaimsInput> {\n if (Object.keys(rules.decisions).length === 0) throw new Error(\"Claims need at least one decision rule\");\n if (Object.values(rules.decisions).some((rule) => rule._tag !== \"Probability\"))\n throw new Error(\"Claims rules must be probability decisions\");\n return {\n name: \"claims\",\n input: claimsInput,\n definitionFor: () => rules,\n inputRef: (input) => `${input.path} › ${input.section.id}`,\n evaluate: (_input, answers, cutoff): ClaimsVerdict =>\n Object.values(Schema.decodeUnknownSync(ClaimsAnswers)(answers))\n .some((answer) => answer.probability >= cutoff) ? \"violation\" : \"pass\",\n };\n}\n\n/** Heads describe only pages outside capture; duplicate ownership fails before evaluation. */\nexport function resolveClaimsDocuments(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n excludeHeads: ReadonlyArray<string> = [],\n): ReadonlyArray<PageDocument> {\n const paths = new Set<string>();\n for (const document of documents) {\n if (paths.has(document.path)) throw new Error(`Duplicate claims page path \"${document.path}\"`);\n paths.add(document.path);\n }\n const seenHeads = new Set<string>();\n const excludes = excludeHeads.map(globToRegExp);\n const extra: Array<PageDocument> = [];\n for (const head of heads) {\n if (seenHeads.has(head.path)) throw new Error(`Duplicate claims head path \"${head.path}\"`);\n seenHeads.add(head.path);\n if (paths.has(head.path)) throw new Error(`Duplicate claims page path \"${head.path}\"`);\n if (excludes.some((pattern) => pattern.test(head.path))) continue;\n const document = { ...head, sections: [], messages: [], facts: [] };\n extra.push({ ...document, hash: hashDocument(document) });\n }\n return [...documents, ...extra];\n}\nfunction sectionMessages(section: SectionDocument): ReadonlyArray<Message> {\n return [\n ...(section.title === undefined ? [] : [section.title]),\n ...section.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...section.items.flatMap((item) => [\n ...(item.title === undefined ? [] : [item.title]),\n ...item.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...item.cells,\n ]),\n ];\n}\n\n/** Source locations do not enter model input or answer identity. */\nexport function claimsInputs(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: Pick<ClaimsOptions, \"context\" | \"excludeHeads\"> = {},\n): ReadonlyArray<ClaimsInput> {\n const excludes = (options.excludeHeads ?? []).map(globToRegExp);\n return resolveClaimsDocuments(documents, heads, options.excludeHeads).flatMap((document) => {\n const sections: ReadonlyArray<SectionDocument> = [\n ...document.sections,\n ...(excludes.some((pattern) => pattern.test(document.path)) ? [] : [{\n id: HEAD_SECTION_ID, kind: \"head\", claimsMarkdown: `${document.title}\\n\\n${document.description}`,\n body: [], items: [], source: document.path, audience: \"all\" as const,\n }]),\n ];\n return sections.map((section) => {\n const evidence = document.sections.filter((entry) => entry.evidence === true && entry !== section)\n .map((entry) => entry.claimsMarkdown ?? sectionMarkdown(entry, { includeHumans: true })).join(\"\\n\\n\");\n return Schema.decodeUnknownSync(claimsInput)({\n path: document.path,\n ...(evidence === \"\" ? {} : { evidence }),\n section: {\n id: section.id, kind: section.kind,\n markdown: section.claimsMarkdown ?? sectionMarkdown(section, { includeHumans: true }),\n messages: sectionMessages(section).map(({ hash, text, facts }) => ({ hash, text, facts })),\n },\n facts,\n ...(options.context === undefined ? {} : { context: options.context }),\n });\n });\n });\n}\n","/**\n * The decision runner. A family supplies its input schema, a definition built\n * from the input (families with dynamic labels build one per input), a way to\n * reference the input in a report, and a pure `evaluate` that maps the model's\n * answers to a verdict. The runner hashes, reads and writes the answer cache,\n * dispatches with bounded concurrency, and buckets confident answers apart from\n * `review`. `runDecisions` asks the model on a miss; `replayDecisions` answers\n * from a committed cache alone and fails on a miss.\n *\n * Build and CLI entries reach Effect and the provider through this module;\n * runtime entries remain Effect-free and Node-free.\n */\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport { DecisionModel } from \"effect/ai\";\nimport type * as AiError from \"effect/ai/AiError\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { cacheKey, inputHash } from \"./answers\";\nimport { readAnswers, writeAnswers, type DecisionCache, type DecisionCacheInvalid } from \"./cache\";\nimport { DecisionRecord, type DecisionBatchReport } from \"./record\";\n\n/**\n * One decision family. `answers` in `evaluate` is erased because each family\n * reads its own `Decision.Answers` shape; the family is the only place that\n * shape is known, and a `DecisionModel` validates it before it arrives.\n */\nexport interface DecisionFamily<Input> {\n readonly name: string;\n readonly input: Schema.ConstraintDecoder<Input>;\n /** Build the definition for one input; dynamic-label families build per input. */\n readonly definitionFor: (input: Input) => Decision.Definition<any, any>;\n /** Stable human reference for the input in a report. */\n readonly inputRef: (input: Input) => string;\n /** Map validated answers to a verdict; `\"review\"` means a human decides. */\n readonly evaluate: (input: Input, answers: any, threshold: number) => string;\n /**\n * Optional input-level guard for constraints a schema cannot express, such as\n * \"dynamic-label families need at least two candidates\". Returns a message to\n * fail the run, or `undefined` when the input is usable.\n */\n readonly validate?: (input: Input) => string | undefined;\n}\n\n/** A probability is confident when it is at or beyond `threshold` on either side. */\nexport const isConfident = (probability: number, threshold: number): boolean =>\n probability >= threshold || probability <= 1 - threshold;\n\n/** True when a probability sits inside the review band `(1 - threshold, threshold)`. */\nexport const inReviewBand = (probability: number, threshold: number): boolean =>\n !isConfident(probability, threshold);\n\n/** True when any probability sits inside the review band. */\nexport const anyInReviewBand = (\n probabilities: ReadonlyArray<number>,\n threshold: number,\n): boolean => probabilities.some((probability) => inReviewBand(probability, threshold));\n\nexport interface RunOptions<Input> {\n readonly family: DecisionFamily<Input>;\n readonly inputs: ReadonlyArray<Input>;\n readonly model: string;\n readonly threshold: number;\n readonly concurrency?: number;\n /** Answer cache; absent means every input is asked. */\n readonly cache?: DecisionCache;\n}\n\n/** Replay found no committed answer for an input. */\nexport class DecisionCacheMiss extends Data.TaggedError(\"DecisionCacheMiss\")<{\n readonly family: string;\n readonly inputRef: string;\n readonly key: string;\n readonly message: string;\n}> {}\n\n/** Group records into resolved/review buckets and count each verdict. */\nexport const buildDecisionReport = (options: {\n readonly family: string;\n readonly model: string;\n readonly threshold: number;\n readonly records: ReadonlyArray<DecisionRecord>;\n}): DecisionBatchReport => {\n const verdicts: Record<string, number> = {};\n const resolved: Array<DecisionRecord> = [];\n const review: Array<DecisionRecord> = [];\n for (const record of options.records) {\n verdicts[record.verdict] = (verdicts[record.verdict] ?? 0) + 1;\n if (record.review) review.push(record);\n else resolved.push(record);\n }\n return {\n kind: \"decide\",\n schemaVersion: 1,\n family: options.family,\n model: options.model,\n threshold: options.threshold,\n counts: {\n inputs: options.records.length,\n resolved: resolved.length,\n review: review.length,\n },\n verdicts,\n resolved,\n review,\n };\n};\n\ninterface Answered {\n readonly answers: unknown;\n readonly usage?: DecisionRecord[\"usage\"];\n}\n\n/**\n * Answer each input from the cache, or through `ask` on a miss, and bucket the\n * results. Inputs run with bounded concurrency.\n */\nconst decideAll = <Input, E, R>(\n options: RunOptions<Input>,\n ask: (input: Input, key: string) => Effect.Effect<Answered, E, R>,\n reuseCache: boolean,\n): Effect.Effect<DecisionBatchReport, E | DecisionCacheInvalid, R> =>\n Effect.gen(function* () {\n const records = yield* Effect.forEach(\n options.inputs,\n (input, index) =>\n Effect.gen(function* () {\n const definition = options.family.definitionFor(input);\n const hash = inputHash(input);\n const key = cacheKey({\n family: options.family.name,\n model: options.model,\n decisions: definition.decisions,\n inputHash: hash,\n });\n const cached =\n options.cache === undefined || !reuseCache\n ? undefined\n : yield* readAnswers(options.cache, key, definition.decisions);\n const answered: Answered = cached === undefined ? yield* ask(input, key) : { answers: cached };\n const verdict = options.family.evaluate(input, answered.answers, options.threshold);\n return {\n decisionId: `${options.family.name}:${index}`,\n schemaVersion: 1,\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n inputHash: hash,\n inputRef: options.family.inputRef(input),\n verdict,\n review: verdict === \"review\",\n answers: answered.answers,\n ...(answered.usage === undefined ? {} : { usage: answered.usage }),\n } satisfies DecisionRecord;\n }),\n { concurrency: options.concurrency ?? 4 },\n );\n return buildDecisionReport({\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n records,\n });\n });\n\n/**\n * Answer every input through the ambient `DecisionModel`. One provider call\n * answers every decision for an input; a cached answer skips the provider unless\n * `refresh` asks again. New answers are written to the cache.\n */\nexport const runDecisions = <Input>(\n options: RunOptions<Input> & { readonly refresh?: boolean },\n): Effect.Effect<\n DecisionBatchReport,\n AiError.AiError | DecisionCacheInvalid,\n DecisionModel.DecisionModel\n> =>\n decideAll(\n options,\n (input, key) =>\n Effect.gen(function* () {\n const response = yield* DecisionModel.decide(options.family.definitionFor(input), { input });\n if (options.cache !== undefined) yield* writeAnswers(options.cache, key, response.answers);\n return {\n answers: response.answers,\n usage: { inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens },\n };\n }),\n options.refresh !== true,\n );\n\n/**\n * Answer every input from the cache alone. Needs no model or credentials, so a\n * gate can run it offline; an input without an answer fails the replay.\n */\nexport const replayDecisions = <Input>(\n options: RunOptions<Input> & { readonly cache: DecisionCache },\n): Effect.Effect<DecisionBatchReport, DecisionCacheMiss | DecisionCacheInvalid> =>\n decideAll(\n options,\n (input, key) =>\n Effect.fail(\n new DecisionCacheMiss({\n family: options.family.name,\n inputRef: options.family.inputRef(input),\n key,\n message: `No ${options.family.name} answer for ${options.family.inputRef(input)}; ask the model and commit the answers.`,\n }),\n ),\n true,\n );\n\nexport const decodeFamilyInputs = <Input>(\n family: DecisionFamily<Input>,\n raw: ReadonlyArray<unknown>,\n): ReadonlyArray<Input> => {\n const decode = Schema.decodeUnknownSync(family.input);\n return raw.map((value) => decode(value));\n};\n","import { resolve } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { DecisionCacheInvalid } from \"./decide/cache\";\n\nimport type { PageHead } from \"./core/page-heads\";\nimport { claimsFamily, claimsInputs, resolveClaimsDocuments, type ClaimsOptions } from \"./decide/families/claims\";\nimport type { DecisionBatchReport } from \"./decide/record\";\nimport { DecisionCacheMiss, replayDecisions, runDecisions } from \"./decide/run\";\nimport type { PageDocument } from \"./markdown/document\";\nimport type { Facts } from \"./markdown/facts\";\n\nexport { claimsFamily, claimsInput, claimsInputs, resolveClaimsDocuments } from \"./decide/families/claims\";\nexport type { ClaimsInput, ClaimsOptions, ClaimsRules, ClaimsVerdict } from \"./decide/families/claims\";\n\nexport interface ClaimsFinding {\n readonly path: string;\n readonly section: string;\n readonly source: string;\n readonly rule: string;\n readonly probability: number;\n}\nexport interface ClaimsReport {\n readonly version: 1;\n readonly mode: \"ask\" | \"replay\";\n readonly pages: number;\n readonly messages: number;\n readonly cached: number;\n readonly asked: number;\n readonly cutoff: number;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n}\nexport class ClaimsFailed extends Data.TaggedError(\"ClaimsFailed\")<{\n readonly message: string;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n readonly report: ClaimsReport;\n}> {}\n\n/** Cutoff affects verdicts alone; committed identity belongs to the decision runner. */\nexport function claimsRunOptions(\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const cutoff = options.cutoff === undefined ? 0.8 : options.cutoff;\n if (!Number.isFinite(cutoff) || cutoff < 0 || cutoff > 1)\n throw new Error(\"Claims cutoff must be between 0 and 1\");\n if (!options.model.trim()) throw new Error(\"Claims model is required\");\n return {\n family: claimsFamily(options.rules),\n inputs: claimsInputs(documents, heads, facts, options),\n model: options.model,\n threshold: cutoff,\n cache: { directory: resolve(root, \".pagegraph/decisions/claims\"), policy: \"committed\" as const },\n };\n}\nconst ProbabilityAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\nfunction claimsReport(\n batch: DecisionBatchReport,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n mode: ClaimsReport[\"mode\"],\n): ClaimsReport {\n const inputs = claimsInputs(documents, heads, facts, options);\n const sources = new Map(documents.map((document) => [document.path, document]));\n const records = [...batch.resolved, ...batch.review];\n const findings = records.flatMap((record): Array<ClaimsFinding> => {\n const index = Number(record.decisionId.slice(\"claims:\".length));\n const input = inputs[index];\n if (input === undefined) throw new Error(`Unknown claims decision ${record.decisionId}`);\n return Object.entries(Schema.decodeUnknownSync(ProbabilityAnswers)(record.answers))\n .flatMap(([rule, answer]) => answer.probability >= batch.threshold ? [{\n path: input.path, section: input.section.id,\n source: sources.get(input.path)?.sections.find((section) => section.id === input.section.id)?.source ?? input.path,\n rule, probability: answer.probability,\n }] : []);\n });\n const asked = records.filter((record) => record.usage !== undefined).length;\n const resolved = resolveClaimsDocuments(documents, heads, options.excludeHeads);\n return {\n version: 1, mode, pages: resolved.length,\n messages: documents.reduce((count, document) => count + document.messages.length, 0),\n cached: records.length - asked, asked, cutoff: batch.threshold, findings,\n };\n}\n\n/** Ask only for absent answers unless refresh is requested, then persist committed answers. */\nexport const runClaims = Effect.fn(\"Claims.run\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n runOptions: { readonly refresh?: boolean } = {},\n) {\n const batch = yield* runDecisions({ ...claimsRunOptions(root, documents, heads, facts, options), ...runOptions });\n return claimsReport(batch, documents, heads, facts, options, \"ask\");\n});\n\n/** Offline gates have no model requirement; absent answers and violations fail with author guidance. */\nexport const replayClaims = Effect.fn(\"Claims.replay\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const batch = yield* replayDecisions(claimsRunOptions(root, documents, heads, facts, options)).pipe(\n Effect.catchTag(\"DecisionCacheInvalid\", (error) => Effect.fail(new DecisionCacheInvalid({\n file: error.file, message: `${error.message} Run pagegraph claims check --refresh and commit the answers.`,\n }))),\n Effect.catchTag(\"DecisionCacheMiss\", (error) => Effect.fail(new DecisionCacheMiss({\n ...error,\n message: `No claims answer for ${error.inputRef} (rules: ${Object.keys(options.rules.decisions).join(\", \")}). Run pagegraph claims check and commit the answers.`,\n }))),\n );\n const report = claimsReport(batch, documents, heads, facts, options, \"replay\");\n if (report.findings.length > 0) return yield* new ClaimsFailed({\n message: report.findings.map((finding) =>\n `Claims violation at ${finding.path} › ${finding.section}: ${finding.rule} (${finding.probability.toFixed(2)}).`,\n ).join(\"\\n\") + \"\\nRun pagegraph claims check and commit the answers.\",\n findings: report.findings, report,\n });\n return report;\n});\n"],"mappings":"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"}
|
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { a as claimsFamily, c as resolveClaimsDocuments, d as DecisionCacheInvalid, i as ClaimsVerdict, l as DecisionCacheMiss, n as ClaimsOptions, o as claimsInput, r as ClaimsRules, s as claimsInputs, t as ClaimsInput, u as DecisionFamily } from "./claims-Bch7Lxlo.js";
|
|
2
|
+
import { a as FactDefinition, o as Facts, p as PageDocument, t as PageHead } from "./page-heads-BfjWQvau.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
//#region src/claims.d.ts
|
|
6
|
+
interface ClaimsFinding {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly section: string;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly rule: string;
|
|
11
|
+
readonly probability: number;
|
|
12
|
+
}
|
|
13
|
+
interface ClaimsReport {
|
|
14
|
+
readonly version: 1;
|
|
15
|
+
readonly mode: "ask" | "replay";
|
|
16
|
+
readonly pages: number;
|
|
17
|
+
readonly messages: number;
|
|
18
|
+
readonly cached: number;
|
|
19
|
+
readonly asked: number;
|
|
20
|
+
readonly cutoff: number;
|
|
21
|
+
readonly findings: ReadonlyArray<ClaimsFinding>;
|
|
22
|
+
}
|
|
23
|
+
declare const ClaimsFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
24
|
+
readonly _tag: "ClaimsFailed";
|
|
25
|
+
} & Readonly<A>;
|
|
26
|
+
declare class ClaimsFailed extends ClaimsFailed_base<{
|
|
27
|
+
readonly message: string;
|
|
28
|
+
readonly findings: ReadonlyArray<ClaimsFinding>;
|
|
29
|
+
readonly report: ClaimsReport;
|
|
30
|
+
}> {}
|
|
31
|
+
/** Cutoff affects verdicts alone; committed identity belongs to the decision runner. */
|
|
32
|
+
declare function claimsRunOptions(root: string, documents: ReadonlyArray<PageDocument>, heads: ReadonlyArray<PageHead>, facts: Facts, options: ClaimsOptions): {
|
|
33
|
+
family: DecisionFamily<{
|
|
34
|
+
readonly section: {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly kind: string;
|
|
37
|
+
readonly markdown: string;
|
|
38
|
+
readonly messages: readonly {
|
|
39
|
+
readonly hash: string;
|
|
40
|
+
readonly text: string;
|
|
41
|
+
readonly facts: readonly string[];
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
readonly facts: {
|
|
45
|
+
readonly [x: string]: {
|
|
46
|
+
readonly text: string;
|
|
47
|
+
readonly kind: "number" | "text" | "money" | "list";
|
|
48
|
+
readonly value: Schema.Json;
|
|
49
|
+
readonly items?: readonly string[] | undefined;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly evidence?: string | undefined;
|
|
54
|
+
readonly context?: string | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
inputs: readonly {
|
|
57
|
+
readonly section: {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly kind: string;
|
|
60
|
+
readonly markdown: string;
|
|
61
|
+
readonly messages: readonly {
|
|
62
|
+
readonly hash: string;
|
|
63
|
+
readonly text: string;
|
|
64
|
+
readonly facts: readonly string[];
|
|
65
|
+
}[];
|
|
66
|
+
};
|
|
67
|
+
readonly facts: {
|
|
68
|
+
readonly [x: string]: {
|
|
69
|
+
readonly text: string;
|
|
70
|
+
readonly kind: "number" | "text" | "money" | "list";
|
|
71
|
+
readonly value: Schema.Json;
|
|
72
|
+
readonly items?: readonly string[] | undefined;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
readonly path: string;
|
|
76
|
+
readonly evidence?: string | undefined;
|
|
77
|
+
readonly context?: string | undefined;
|
|
78
|
+
}[];
|
|
79
|
+
model: string;
|
|
80
|
+
threshold: number;
|
|
81
|
+
cache: {
|
|
82
|
+
directory: string;
|
|
83
|
+
policy: "committed";
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/** Ask only for absent answers unless refresh is requested, then persist committed answers. */
|
|
87
|
+
declare const runClaims: (root: string, documents: readonly PageDocument[], heads: readonly PageHead[], facts: Readonly<Record<string, FactDefinition>>, options: ClaimsOptions, runOptions?: {
|
|
88
|
+
readonly refresh?: boolean;
|
|
89
|
+
} | undefined) => Effect.Effect<ClaimsReport, import("effect/ai/AiError").AiError | DecisionCacheInvalid, import("effect/ai/DecisionModel").DecisionModel>;
|
|
90
|
+
/** Offline gates have no model requirement; absent answers and violations fail with author guidance. */
|
|
91
|
+
declare const replayClaims: (root: string, documents: readonly PageDocument[], heads: readonly PageHead[], facts: Readonly<Record<string, FactDefinition>>, options: ClaimsOptions) => Effect.Effect<ClaimsReport, ClaimsFailed | DecisionCacheInvalid | DecisionCacheMiss, never>;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { ClaimsFailed, ClaimsFinding, type ClaimsInput, type ClaimsOptions, ClaimsReport, type ClaimsRules, type ClaimsVerdict, claimsFamily, claimsInput, claimsInputs, claimsRunOptions, replayClaims, resolveClaimsDocuments, runClaims };
|
|
94
|
+
//# sourceMappingURL=claims.d.ts.map
|
package/dist/claims.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as claimsFamily, c as resolveClaimsDocuments, i as runClaims, n as claimsRunOptions, o as claimsInput, r as replayClaims, s as claimsInputs, t as ClaimsFailed } from "./claims-Bv7voeEV.js";
|
|
2
|
+
export { ClaimsFailed, claimsFamily, claimsInput, claimsInputs, claimsRunOptions, replayClaims, resolveClaimsDocuments, runClaims };
|