@crewhaus/provability 0.0.0-stage → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,423 @@
1
+ /**
2
+ * The vacuous-pass backstop (design §2, §0 A and B).
3
+ *
4
+ * An answer with no claims passes every floor with nothing to prove — the
5
+ * right verdict for a greeting, a clarifying question or a code block, and
6
+ * the wrong one for an answer whose claim the extractor failed to see.
7
+ * Review rounds one and two each found new shapes that merged a claim into
8
+ * a question or a heading — "30 days [1] — anything else?", "1,284.60; want
9
+ * the PDF?", a no-break space, a `<br>`, U+2028, a table row, a heading over
10
+ * a question — and claim-ledger's `extractClaims` saw none of them, so the
11
+ * gate served scenario A or B with a ✓. Each was fixed shape by shape, and
12
+ * each fix left the next shape open. So the verdict does not rest on the
13
+ * extractor alone: an answer may be vacuous only when what a reader sees
14
+ * of it, code and definition lines aside, carries
15
+ *
16
+ * - no citation marker at all, and
17
+ * - no figure outside a question,
18
+ *
19
+ * read by a segmenter far blunter than the extractor, which cuts at every
20
+ * line break (U+0085, U+2028, U+2029, `\f`, `\v` and `<br>` included),
21
+ * every sentence terminator whether a space follows it or not, every
22
+ * semicolon, em or en dash and table pipe — never at a comma. A heading
23
+ * is a title to the figure rule but for what it states after a label
24
+ * (claim-ledger's `headingClaim` reads the headings that state one); a
25
+ * marker in it still counts. Otherwise
26
+ * each segment that carries a marker, and each that states a figure and is
27
+ * no question, is a claim, verified like any other: "30 days" cut off a
28
+ * trailing "anything else?" is checked against the page, and fails where
29
+ * the page says 14.
30
+ *
31
+ * A question stays a question. A piece of text that ends in `?` is a
32
+ * question from the first segment in it that opens as one ("Which of the 4
33
+ * plans do you mean — Basic or Pro?", "Should I book 2 rooms\nfor Monday?")
34
+ * or ends as one; what comes before that segment is said, not asked ("The
35
+ * refund window is 30 days — anything else?"). A clarifying question with a
36
+ * figure in it ("Do you want 2 or 3 copies?"), a greeting, and an answer
37
+ * that is only code stay vacuous.
38
+ */
39
+ import { citationMarkers, codeSpans, extractNumbers, isAssumptionMarker, lineIndex, plainClaim, } from "@crewhaus/claim-ledger";
40
+ /** A definition line (`[1]: …`, `[^a]: …`), up to three spaces in. */
41
+ const DEFINITION_LINE = /^[ \t]{0,3}\[\^?[^\]\s]+\]:[^\n]*/gm;
42
+ /** A URL: its dots and slashes are not sentence ends. */
43
+ const URL_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/[^\s<>"'`]+/gi;
44
+ /** An HTML line break. */
45
+ const BREAK_TAG = /<br\s*\/?>/gi;
46
+ /**
47
+ * A list item's marker at the start of a line — `1.`, `2)`, `-`, `*`, `+`,
48
+ * a bold `**1.**` — inside block quotes too: a reader sees an item's number,
49
+ * not a figure the answer states ("1. Open Settings").
50
+ */
51
+ const LIST_MARKER = /^([ \t]*(?:>[ \t]?)*[ \t]*)((?:\*\*|__)?(?:\d{1,9}[.)]|[-*+])(?:\*\*|__)?)(?=[ \t])/gm;
52
+ /** Every character that breaks a line. */
53
+ const LINE_BREAK = /[\n\r\v\f\u0085\u2028\u2029]/;
54
+ /** What may close a sentence after its terminator: quotes, brackets, emphasis. */
55
+ const CLOSERS = /["'”’»)\]*_~`]/;
56
+ /**
57
+ * Words a question opens with — the interrogatives, the auxiliaries an
58
+ * inverted question starts on, and the openers of an elliptical one ("Want
59
+ * the PDF?", "Any of these work?"). A conjunction is not one: "And the
60
+ * window is 30 days; want details?" says something before it asks.
61
+ */
62
+ const QUESTION_OPENERS = new Set([
63
+ "what",
64
+ "which",
65
+ "who",
66
+ "whom",
67
+ "whose",
68
+ "when",
69
+ "where",
70
+ "why",
71
+ "how",
72
+ "whether",
73
+ "do",
74
+ "does",
75
+ "did",
76
+ "don't",
77
+ "doesn't",
78
+ "didn't",
79
+ "is",
80
+ "isn't",
81
+ "are",
82
+ "aren't",
83
+ "was",
84
+ "wasn't",
85
+ "were",
86
+ "weren't",
87
+ "am",
88
+ "can",
89
+ "can't",
90
+ "could",
91
+ "couldn't",
92
+ "will",
93
+ "won't",
94
+ "would",
95
+ "wouldn't",
96
+ "shall",
97
+ "should",
98
+ "shouldn't",
99
+ "may",
100
+ "might",
101
+ "must",
102
+ "have",
103
+ "haven't",
104
+ "has",
105
+ "hasn't",
106
+ "had",
107
+ "want",
108
+ "wanna",
109
+ "need",
110
+ "any",
111
+ "anything",
112
+ "anyone",
113
+ "anybody",
114
+ "else",
115
+ "ok",
116
+ "okay",
117
+ "please",
118
+ ]);
119
+ /** A sentence terminator. */
120
+ const TERMINATOR = /[.!?…]/;
121
+ /** What may open the segment glued to a terminator: a quote, a bracket, emphasis. */
122
+ const OPENERS = /["'“‘(\[*_]/;
123
+ /**
124
+ * The answer as the segmenter reads it, offsets kept (one character per
125
+ * UTF-16 unit): code (fenced and inline), definition lines, URLs and list
126
+ * item markers blanked, `<br>` turned into a line break.
127
+ */
128
+ function visibleText(text) {
129
+ const out = text.split("");
130
+ const blank = (from, to) => {
131
+ for (let i = from; i < to; i++)
132
+ if (out[i] !== "\n")
133
+ out[i] = " ";
134
+ };
135
+ for (const [from, to] of codeSpans(text))
136
+ blank(from, to);
137
+ for (const m of text.matchAll(DEFINITION_LINE)) {
138
+ if (m.index !== undefined)
139
+ blank(m.index, m.index + m[0].length);
140
+ }
141
+ for (const m of text.matchAll(URL_TEXT)) {
142
+ if (m.index !== undefined)
143
+ blank(m.index, m.index + m[0].length);
144
+ }
145
+ for (const m of text.matchAll(LIST_MARKER)) {
146
+ if (m.index === undefined)
147
+ continue;
148
+ const from = m.index + m[1].length;
149
+ blank(from, from + m[2].length);
150
+ }
151
+ for (const m of text.matchAll(BREAK_TAG)) {
152
+ if (m.index === undefined)
153
+ continue;
154
+ blank(m.index, m.index + m[0].length);
155
+ out[m.index] = "\n";
156
+ }
157
+ return out.join("");
158
+ }
159
+ const isSpace = (c) => c !== undefined && /\s/u.test(c);
160
+ const isLetter = (c) => c !== undefined && /\p{L}/u.test(c);
161
+ const isDigit = (c) => c !== undefined && /\p{N}/u.test(c);
162
+ const isBreak = (c) => c !== undefined && LINE_BREAK.test(c);
163
+ /** Where the closers after a terminator that ends at `at` end. */
164
+ function pastClose(text, at) {
165
+ let i = at;
166
+ while (i < text.length && CLOSERS.test(text[i]))
167
+ i++;
168
+ return i;
169
+ }
170
+ /**
171
+ * Past the markers that follow a sentence end on its line (`[1]` after
172
+ * "rose 12%." is that sentence's), by where each marker the citation
173
+ * grammar reads (`citationMarkers`) ends, keyed by where it starts.
174
+ */
175
+ function pastMarkers(text, at, markers) {
176
+ let i = at;
177
+ for (;;) {
178
+ let j = i;
179
+ while (j < text.length && isSpace(text[j]) && !isBreak(text[j]))
180
+ j++;
181
+ const end = markers.get(j);
182
+ if (end === undefined)
183
+ return i;
184
+ i = end;
185
+ }
186
+ }
187
+ /**
188
+ * Is the `.` at `at` glued to a word that starts a new segment —
189
+ * "days.Anything", "1,284.50.want" — rather than inside a name ("Node.js",
190
+ * "e.g.", "policy.md")? A capital starts one; so does a lowercase word of
191
+ * four letters or more. A decimal point never does.
192
+ */
193
+ function gluedFullStop(text, at) {
194
+ if (isDigit(text[at - 1]) && isDigit(text[at + 1]))
195
+ return false;
196
+ if (!isLetter(text[at + 1]))
197
+ return false;
198
+ let end = at + 1;
199
+ while (end < text.length && isLetter(text[end]))
200
+ end++;
201
+ const word = text.slice(at + 1, end);
202
+ return /^\p{Lu}/u.test(word) || word.length >= 4;
203
+ }
204
+ /**
205
+ * The answer cut into pieces — at a terminator a space or a line break
206
+ * follows, and at a blank line — and each piece into segments: at every
207
+ * other line break, at a terminator glued to the next word, and at every
208
+ * semicolon, em dash, en dash (not one between figures, a range) and pipe.
209
+ * Over {@link visibleText}.
210
+ */
211
+ function segments(text) {
212
+ const out = [];
213
+ const markers = new Map(citationMarkers(text).map((m) => [m.at, m.end]));
214
+ let pieceStart = 0;
215
+ let partStart = 0;
216
+ let parts = [];
217
+ const cutPart = (end, next) => {
218
+ parts.push([partStart, end]);
219
+ partStart = next;
220
+ };
221
+ const cutPiece = (end, next) => {
222
+ cutPart(end, next);
223
+ out.push({ piece: [pieceStart, end], parts });
224
+ parts = [];
225
+ pieceStart = next;
226
+ };
227
+ let i = 0;
228
+ while (i < text.length) {
229
+ const c = text[i];
230
+ if (TERMINATOR.test(c)) {
231
+ let end = i + 1;
232
+ while (end < text.length && TERMINATOR.test(text[end]))
233
+ end++;
234
+ const closed = pastClose(text, end);
235
+ if (closed >= text.length || isSpace(text[closed])) {
236
+ const after = pastMarkers(text, closed, markers);
237
+ cutPiece(after, after);
238
+ i = after;
239
+ continue;
240
+ }
241
+ const next = text[closed];
242
+ const glued = c === "." && end === i + 1
243
+ ? gluedFullStop(text, i)
244
+ : isLetter(next) || isDigit(next) || OPENERS.test(next);
245
+ if (glued)
246
+ cutPart(closed, closed);
247
+ i = glued ? closed : end;
248
+ continue;
249
+ }
250
+ if (isBreak(c)) {
251
+ let j = c === "\r" && text[i + 1] === "\n" ? i + 2 : i + 1;
252
+ while (j < text.length && /[ \t]/.test(text[j]))
253
+ j++;
254
+ if (j >= text.length || isBreak(text[j])) {
255
+ // A blank line: the piece ends.
256
+ let next = j;
257
+ while (next < text.length && isSpace(text[next]))
258
+ next++;
259
+ cutPiece(i, next);
260
+ i = next;
261
+ }
262
+ else {
263
+ cutPart(i, i + 1);
264
+ i += 1;
265
+ }
266
+ continue;
267
+ }
268
+ const range = c === "–" && isDigit(text[i - 1]) && isDigit(text[i + 1]);
269
+ if (c === ";" || c === "|" || c === "—" || (c === "–" && !range))
270
+ cutPart(i, i + 1);
271
+ i += 1;
272
+ }
273
+ if (pieceStart < text.length)
274
+ cutPiece(text.length, text.length);
275
+ return out;
276
+ }
277
+ /** An ATX heading line, and a setext underline. */
278
+ const ATX_HEADING = /^[ \t]{0,3}#{1,6}(?:[ \t]|$)/;
279
+ const SETEXT_UNDERLINE = /^[ \t]{0,3}(?:=+|-{2,})[ \t]*$/;
280
+ /** A line a list item or a block quote opens: its `---` is a break, not an underline. */
281
+ const BLOCK_OPENER = /^[ \t]*(?:[-*+>]|\d{1,9}[.)])[ \t]/;
282
+ /**
283
+ * The heading lines of `text`, as `[from, to)` spans: a line `#` opens, and
284
+ * the paragraph line a setext underline (`===`, `---`) sits under.
285
+ */
286
+ function headingLines(text) {
287
+ const out = [];
288
+ let previous;
289
+ let at = 0;
290
+ for (const line of text.split("\n")) {
291
+ const span = [at, at + line.length];
292
+ if (ATX_HEADING.test(line))
293
+ out.push(span);
294
+ else if (SETEXT_UNDERLINE.test(line) &&
295
+ previous !== undefined &&
296
+ text.slice(...previous).trim() !== "" &&
297
+ !BLOCK_OPENER.test(text.slice(...previous)) &&
298
+ !SETEXT_UNDERLINE.test(text.slice(...previous))) {
299
+ out.push(previous);
300
+ }
301
+ previous = span;
302
+ at += line.length + 1;
303
+ }
304
+ return out;
305
+ }
306
+ /** What a heading states after a label ("Invoice total: 1,284.50"), or nothing. */
307
+ function labelled(heading) {
308
+ const colon = heading.indexOf(":");
309
+ return colon === -1 ? "" : heading.slice(colon + 1);
310
+ }
311
+ /** `text[from, to)` with its edges trimmed, as a span. */
312
+ function trimmed(text, [from, to]) {
313
+ let a = from;
314
+ let b = to;
315
+ while (a < b && isSpace(text[a]))
316
+ a++;
317
+ while (b > a && isSpace(text[b - 1]))
318
+ b--;
319
+ return [a, b];
320
+ }
321
+ /** Does `text` end as a question once its closers and the markers after it are set aside? */
322
+ function endsAsQuestion(text) {
323
+ const ends = new Map(citationMarkers(text).map((m) => [m.end, m.at]));
324
+ let end = text.length;
325
+ while (end > 0) {
326
+ const mark = ends.get(end);
327
+ if (mark !== undefined)
328
+ end = mark;
329
+ else if (isSpace(text[end - 1]) || CLOSERS.test(text[end - 1]))
330
+ end -= 1;
331
+ else
332
+ break;
333
+ }
334
+ return text[end - 1] === "?";
335
+ }
336
+ /** Does `text` open as a question: its first word, past bullets, figures and marks, an opener? */
337
+ function opensAsQuestion(text) {
338
+ const word = /[\p{L}][\p{L}'’]*/u.exec(text)?.[0];
339
+ return word !== undefined && QUESTION_OPENERS.has(word.toLowerCase().replace(/’/g, "'"));
340
+ }
341
+ /**
342
+ * Claims the backstop reads in an answer the extractor found nothing to
343
+ * count in (see the module doc), numbered from `firstIndex`. Each is the
344
+ * segment as written (`raw`, markers in it), read as `plainClaim` reads a
345
+ * sentence, citing the markers it carries — a marker the document defines,
346
+ * a numeral, a footnote or an assumption id; a segment that is only
347
+ * markers gives them to the segment before it. A segment the extractor
348
+ * already returned (an assumption it read) is not read twice. Empty when
349
+ * the answer may be vacuous.
350
+ */
351
+ export function backstopClaims(text, definitions, extracted, firstIndex) {
352
+ const visible = visibleText(text);
353
+ const lineOf = lineIndex(text);
354
+ // A heading is a title to the figure rule — "## Refund options for 2024"
355
+ // over "Which option do you want?" has nothing to prove; claim-ledger's
356
+ // `headingClaim` decides which heading states one. What one states after
357
+ // a label ("## Invoice total: 1,284.50") is read, and a marker in one
358
+ // still makes it a claim.
359
+ const headings = headingLines(visible);
360
+ const inHeading = ([from]) => headings.some(([a, b]) => from >= a && from < b);
361
+ const known = extracted.map((claim) => claim.text.replace(/\s+/g, " ").trim().toLowerCase());
362
+ const citing = (id, caret) => definitions.has(id) || caret || /^\d+$/.test(id) || isAssumptionMarker(id);
363
+ const out = [];
364
+ for (const { piece, parts: spans } of segments(visible)) {
365
+ const pieceText = visible.slice(...trimmed(visible, piece));
366
+ const asked = endsAsQuestion(pieceText);
367
+ const parts = [];
368
+ // In a piece that ends as a question: asked from the segment that
369
+ // opens one to the segment that ends one; said before it.
370
+ let questioning = false;
371
+ for (const raw of spans) {
372
+ const span = trimmed(visible, raw);
373
+ const shown = visible.slice(...span);
374
+ if (shown === "")
375
+ continue;
376
+ const markers = [];
377
+ for (const m of citationMarkers(shown)) {
378
+ if (citing(m.marker, m.caret) && !markers.includes(m.marker))
379
+ markers.push(m.marker);
380
+ }
381
+ const words = plainClaim(shown);
382
+ if (words === "" && markers.length > 0 && parts.length > 0) {
383
+ // Markers on their own: the segment before them is what they cite.
384
+ const before = parts[parts.length - 1];
385
+ for (const marker of markers)
386
+ if (!before.markers.includes(marker))
387
+ before.markers.push(marker);
388
+ before.span = [before.span[0], span[1]];
389
+ continue;
390
+ }
391
+ const ends = endsAsQuestion(shown);
392
+ if (asked && opensAsQuestion(words))
393
+ questioning = true;
394
+ parts.push({
395
+ span,
396
+ markers,
397
+ figure: extractNumbers(inHeading(span) ? labelled(words) : words).length > 0,
398
+ question: questioning || ends,
399
+ });
400
+ if (ends)
401
+ questioning = false;
402
+ }
403
+ for (const part of parts) {
404
+ if (part.markers.length === 0 && (!part.figure || part.question))
405
+ continue;
406
+ const raw = text.slice(...part.span).trim();
407
+ const claimText = plainClaim(raw);
408
+ if (claimText === "")
409
+ continue;
410
+ const folded = claimText.replace(/\s+/g, " ").trim().toLowerCase();
411
+ if (known.some((seen) => seen.includes(folded) || folded.includes(seen)))
412
+ continue;
413
+ out.push({
414
+ index: firstIndex + out.length,
415
+ text: claimText,
416
+ raw,
417
+ line: lineOf(part.span[0]),
418
+ markers: part.markers,
419
+ });
420
+ }
421
+ }
422
+ return out;
423
+ }
@@ -0,0 +1,176 @@
1
+ import { type ProofBundle, type ProofClaim, type ProvabilityConfig, type UnmetReason, type VerifyOptions } from "./types";
2
+ /**
3
+ * Distinct URLs one verification sends a HEAD to under `sources.resolve:
4
+ * head` (design §6.1). The answer is model-written, so without a ceiling it
5
+ * decides how many outbound requests the gate makes and how long it waits
6
+ * on them; a URL past the ceiling is `unresolved` — not checked is not
7
+ * reachable — and the detail names the cap.
8
+ */
9
+ export declare const MAX_HEAD_REQUESTS = 50;
10
+ /**
11
+ * How many of those HEADs are in flight at once, and how long one
12
+ * verification waits on them in all (design §6.1, §18: the gate's cost is
13
+ * bounded). Sent one at a time under a per-request timeout alone, fifty
14
+ * URLs on hosts that never answer held one pass for minutes, and again on
15
+ * every retry; past the deadline a URL is `unresolved` — not checked is not
16
+ * reachable — whatever is still pending.
17
+ */
18
+ export declare const HEAD_CONCURRENCY = 6;
19
+ export declare const HEAD_DEADLINE_MS = 15000;
20
+ /**
21
+ * Fetched text one verification reads while grounding, in tokens: each
22
+ * claim reads every body it cites end to end, so a long answer citing a
23
+ * large page costs claims × body — 200 claims over a 4 MB page held the
24
+ * harness thread for twelve seconds per pass. Past this a source binding
25
+ * is not read for grounding: it reaches `cited` and, at a higher floor,
26
+ * reads `below_floor` with a detail naming the cap (§18: bounded gate
27
+ * cost). The verifier also yields to the event loop between claims, so
28
+ * the work it does do never holds the process for long at a stretch.
29
+ */
30
+ export declare const MAX_GROUNDING_TOKENS = 40000000;
31
+ /** Test seam: a smaller grounding budget, or (no argument) {@link MAX_GROUNDING_TOKENS} again. */
32
+ export declare function _setGroundingBudgetForTest(tokens?: number): void;
33
+ /**
34
+ * Whitespace collapsed, cut to `max` with an ellipsis. Only a prefix of a
35
+ * long text is collapsed — a 2 MB tool result clipped to 240 characters
36
+ * must not be rewritten whole — unless that prefix is so full of
37
+ * whitespace that it collapses to fewer than `max` characters.
38
+ */
39
+ export declare function clip(text: string, max: number): string;
40
+ /**
41
+ * At most `max` characters of `text` around `[from, to)`, whitespace
42
+ * collapsed, an ellipsis marking each cut: the frozen excerpt of a long
43
+ * body or result, centred on what met the floor (design §5.3: a bundle
44
+ * stays human-checkable after the body is gone, so its excerpt must show
45
+ * the figure or the words that grounded the claim, not the text's first
46
+ * 240 characters). When `[from, to)` itself is longer, its start is kept.
47
+ */
48
+ export declare function clipAround(text: string, from: number, to: number, max: number): string;
49
+ /**
50
+ * The reasons a claim is unmet: `no_citation` for a claim with no marker at
51
+ * all (it has zero bindings — the representation design §5.1's nudge reads),
52
+ * else the reason of every binding that is not `ok`, in order, each once. An
53
+ * empty list for a met or assumption claim.
54
+ */
55
+ export declare function claimReasons(claim: ProofClaim): UnmetReason[];
56
+ /**
57
+ * Is a recorded tool name on `execution.allow`? The IR carries CANONICAL
58
+ * entries (ir `IrProvability.execution.allow`): a builtin is its spec key
59
+ * (`moneyAdd`), anything else its exact name. A recorded builtin name
60
+ * (`MoneyAdd`) is mapped to its key case-insensitively, the way the compiler
61
+ * maps `tools:`; a non-builtin name (`mcp__calc__add`) must match exactly,
62
+ * because MCP tool names are case-sensitive.
63
+ */
64
+ export declare function isToolAllowed(toolName: string, allow: ReadonlyArray<string>): boolean;
65
+ /**
66
+ * Result tokens the figure polarity rule has read into a statement's table
67
+ * of words: for a test to count with, as claim-ledger's `scanMeter` is.
68
+ * Only ever added to.
69
+ */
70
+ export declare const polarityMeter: {
71
+ tokens: number;
72
+ };
73
+ /**
74
+ * Why the answer's sentence does not state what a proof's recorded claim
75
+ * says, or `undefined` when it does (design §6.4): "the grounded coverage
76
+ * test", read the way `grounded` reads it — the words, AND the figures and
77
+ * the negations that coverage alone cannot see.
78
+ *
79
+ * - Words: the artifact claim's content tokens covered by the sentence's
80
+ * at the grounded bar, so an accepted proof of something else cannot be
81
+ * pinned to this sentence.
82
+ * - Figures: the same figures on both sides (`two` and `2` are one). A
83
+ * proof about meetings 1 and 2 does not prove a sentence about all six,
84
+ * and `contentTokens` drops one- and two-digit numbers.
85
+ * - Polarity, word by word: each word of the proof's claim (a figure by its
86
+ * value) is denied on both sides or on neither, as claim-ledger's
87
+ * `governingNegation` scopes a negation, wherever the sentence uses it.
88
+ * `contentTokens` drops "no" (two letters) and "not" (a stopword), so
89
+ * without this an accepted proof that two of the six meetings overlap
90
+ * would prove "no two of the six meetings overlap" — acceptance scenario
91
+ * C served false. Read per word, not per sentence, because the sentence
92
+ * may say more than the proof (design §6.4): "no discount was applied, so
93
+ * the invoice total is 1,284.50 EUR" states the proved total, and its
94
+ * "no" denies a discount the proof never mentions. A word one side both
95
+ * states and denies is not compared, and a negation that moves from one
96
+ * word to another ("no two of the meetings overlap", "two of the
97
+ * meetings didn't overlap") is a different statement. A partitive denial
98
+ * of the whole statement ("none of the 3 invoices is overdue") is read
99
+ * too ({@link partitiveDenial}), and so is a frame that negates it ("it
100
+ * is not true that …", {@link negationFrames}): one side framed and the
101
+ * other not states the opposite (acceptance C).
102
+ *
103
+ * What it cannot see is a sentence that adds a clause of its own to the
104
+ * proved statement ("…, and the CEO approved it"): the coverage runs from
105
+ * the proof's claim into the sentence, as the design specifies, so the
106
+ * sentence may carry more words. That residual, like the formalization gap
107
+ * itself, is shown beside the claim (the statement), not closed here.
108
+ */
109
+ export declare function statementMismatch(proved: string, sentence: string, bar: number): string | undefined;
110
+ /**
111
+ * An evidence id as the model may have dressed it — `` tool:`toolu_01` ``,
112
+ * `proof:prf_….` at the end of a sentence — down to the id. Ids are
113
+ * `[A-Za-z0-9_-]` runs, so wrapping quotes, backticks and angle brackets and
114
+ * trailing sentence punctuation are never part of one. Trimmed by walking
115
+ * in from each end: an end-anchored pattern (`/[…]+$/`) restarts at every
116
+ * character of a long run of them that does not end the id — quadratic in
117
+ * text the model writes, six seconds for 80 000 dots.
118
+ */
119
+ export declare function bareId(id: string): string;
120
+ /**
121
+ * What `grounded.independent` counts as one origin: the registrable domain
122
+ * of a URL (`docs.acme.example` and `www.acme.example` are one publisher), a
123
+ * DOI's registrant prefix (`10.1000` — one publisher's papers are one
124
+ * origin, like one site's pages), and a workspace file's own path.
125
+ */
126
+ export declare function independenceKey(origin: string): string;
127
+ /**
128
+ * `sha256:<hex>` over the canonical JSON (keys sorted recursively) of every
129
+ * field of the bundle except `bundleHash` itself — what the audit record
130
+ * and the trace line carry, so a stored bundle can be shown to be the one
131
+ * that was graded.
132
+ */
133
+ export declare function bundleHash(bundle: Omit<ProofBundle, "bundleHash"> | ProofBundle): string;
134
+ /**
135
+ * The bundle for a verifier that THREW (design §5.1): grading
136
+ * infrastructure failed, the gate fails open, and the bundle says so rather
137
+ * than printing a ✓ — `verdict: "unverified"`, no claims, the error clipped.
138
+ * Only the floor and the threshold are read, so the runtime gate — which
139
+ * holds `RunProvability`, not the lowered IR block — can build one too.
140
+ */
141
+ export declare function unverifiedBundle(config: Pick<ProvabilityConfig, "level" | "threshold">, error: unknown, retryIndex?: number, now?: Date | (() => Date)): ProofBundle;
142
+ /**
143
+ * Citations the up-front HEAD collection in {@link verifyDocument} has
144
+ * examined: for a test to count with, as claim-ledger's `scanMeter` is.
145
+ * Only ever added to.
146
+ */
147
+ export declare const headCollectMeter: {
148
+ markers: number;
149
+ };
150
+ /**
151
+ * Verify every claim of `text` against `opts.evidence` at `opts.config`'s
152
+ * floor, and return the proof bundle (design §5.3).
153
+ *
154
+ * - Claims are claim-ledger's `extractClaims` — plus the text any
155
+ * definition line shows beyond what it names, read as claims citing that
156
+ * definition (see `definitionClaims`); a claim with no marker is unmet
157
+ * with zero bindings (`no_citation`), one with a dangling marker is unmet
158
+ * (`no_definition`).
159
+ * - Each marker binds to its definition, classified by claim-ledger's
160
+ * evidence-ref grammar: a source (cited → grounded), `tool:` (checked),
161
+ * `proof:` (proven) or `assume:` (an assumption).
162
+ * - A binding records the highest rung it reached and, when that is below
163
+ * the floor, the FIRST check it failed. Rungs above the floor are still
164
+ * computed — `achieved` is "the highest floor this answer would have
165
+ * passed" — but the three outside calls are made only where the floor
166
+ * needs them: `resolveHead` at `cited`/`grounded` (no source can meet a
167
+ * higher floor), `judgeWaiver` at `grounded` (for a coverage-only
168
+ * paraphrase miss), `replay` at `checked`. So `achieved` above the floor
169
+ * is computed without them.
170
+ * - `scope: turn` (`turnNumbers`) restricts the evidence to those turns.
171
+ *
172
+ * Never throws for anything a document or its evidence can contain — an
173
+ * unreadable artifact, a failing judge or replay, a refused HEAD are each a
174
+ * binding's reason. Throws only for a malformed config (a programmer error).
175
+ */
176
+ export declare function verifyDocument(text: string, opts: VerifyOptions): Promise<ProofBundle>;