@crewhaus/tool-proof 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,260 @@
1
+ import { sha256Of } from "@crewhaus/provability";
2
+ /**
3
+ * Input bounds. The statement and proof bounds are the largest any shipped
4
+ * checker accepts (`BUILTIN_LIMITS.maxStatementChars`, `.maxProofChars`), so
5
+ * the schema never refuses what a checker would take; each checker applies
6
+ * its own tighter limit and says so. A claim is one sentence of an answer.
7
+ */
8
+ export const PROOF_INPUT_LIMITS = {
9
+ checkerChars: 64,
10
+ claimChars: 2_000,
11
+ statementChars: 20_000,
12
+ proofChars: 1_000_000,
13
+ };
14
+ /** Checker output kept in an artifact (proof-checker's "a few KB"). */
15
+ export const OUTPUT_CHARS = 4_000;
16
+ /** A counterexample or a reason, as recorded. */
17
+ const DETAIL_CHARS = 2_000;
18
+ /**
19
+ * The most UTF-8 bytes a proof tool's report may be. The runtime spills any
20
+ * tool result over 10 KB (`@crewhaus/tool-result-store`'s
21
+ * `DEFAULT_THRESHOLD_BYTES`, 10 240) to a file and shows the model only its
22
+ * first 10 KB, so a report past that loses its tail — and the tail is where
23
+ * a JSON report's instructions would sit. Every report is cut to fit under
24
+ * it, with the instructions placed first, so the model always reads the
25
+ * whole of it.
26
+ */
27
+ export const REPORT_BYTES = 9_000;
28
+ /** How a ProofCheck report spends its bytes; `output` gets what is left. */
29
+ const REPORT_REASON_BYTES = 1_200;
30
+ const REPORT_COUNTEREXAMPLE_BYTES = 1_800;
31
+ const REPORT_LIST_BYTES = 2_400;
32
+ /** One assumption or axiom as listed (the artifact keeps each whole). */
33
+ const LISTED_CHARS = 300;
34
+ /**
35
+ * How much longer than `formal.timeoutMs` one check may take before it is
36
+ * recorded as a rejection: a container start and the checker's own
37
+ * reporting. The sandbox checkers stop themselves at `formal.timeoutMs`;
38
+ * this is the backstop for a plugin checker that never settles.
39
+ */
40
+ export const CHECK_GRACE_MS = 30_000;
41
+ /** Keep the head and the tail of long text, marking the cut. */
42
+ export function clipMiddle(text, cap) {
43
+ if (text.length <= cap)
44
+ return text;
45
+ const head = text.slice(0, Math.floor(cap * 0.7));
46
+ const tail = text.slice(text.length - Math.floor(cap * 0.25));
47
+ return `${head}\n… ${text.length - head.length - tail.length} characters omitted …\n${tail}`;
48
+ }
49
+ /** Text clipped to `cap` characters, an ellipsis marking the cut. */
50
+ export function clip(text, cap) {
51
+ return text.length <= cap ? text : `${text.slice(0, cap - 1)}…`;
52
+ }
53
+ /** The UTF-8 bytes `value` takes in a compact JSON report. */
54
+ export function jsonBytes(value) {
55
+ return Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
56
+ }
57
+ /**
58
+ * `text` cut (by `cut`, `clipMiddle` by default) until it takes at most
59
+ * `maxBytes` as a JSON string. Measured, not estimated: escaping turns a
60
+ * newline into two bytes and a control character into six, and a character
61
+ * can take three bytes of UTF-8. Empty when not even the cut marker fits.
62
+ */
63
+ export function fitText(text, maxBytes, cut = clipMiddle) {
64
+ if (jsonBytes(text) <= maxBytes)
65
+ return text;
66
+ let cap = text.length;
67
+ for (let attempt = 0; attempt < 12 && cap > 0; attempt++) {
68
+ const candidate = cut(text, cap);
69
+ const size = jsonBytes(candidate);
70
+ if (size <= maxBytes)
71
+ return candidate;
72
+ cap = Math.min(cap - 1, Math.floor((cap * maxBytes) / size) - 8);
73
+ }
74
+ return "";
75
+ }
76
+ const strings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
77
+ const text = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
78
+ const errorMessage = (err) => (err instanceof Error ? err.message : String(err));
79
+ const CANCELLED = Symbol("cancelled");
80
+ const TIMED_OUT = Symbol("timed out");
81
+ /**
82
+ * Run `checker` and return a well-formed `CheckResult`, whatever it does.
83
+ * A checker that throws has not proved anything: the result is a rejection
84
+ * whose reason names the failure, and it is persisted like any rejection.
85
+ * So is one that does not settle within `timeoutMs`. An abort of `signal`
86
+ * stops the wait too, with a rejection the caller recognises by the aborted
87
+ * signal and does not save. Neither stops a checker that ignores its signal
88
+ * — nothing can — but the call returns, so one hung plugin cannot hang the
89
+ * turn.
90
+ */
91
+ export async function runChecker(checker, input, opts = {}) {
92
+ const { signal, timeoutMs } = opts;
93
+ const started = performance.now();
94
+ const elapsed = () => Math.round(performance.now() - started);
95
+ const rejection = (reason) => ({
96
+ ok: false,
97
+ checker: checker.id,
98
+ assumptions: [],
99
+ axioms: [],
100
+ output: "",
101
+ reason,
102
+ durationMs: elapsed(),
103
+ });
104
+ if (signal?.aborted === true)
105
+ return rejection("the check was cancelled");
106
+ let timer;
107
+ let onAbort;
108
+ const stops = [];
109
+ if (signal !== undefined) {
110
+ stops.push(new Promise((resolve) => {
111
+ onAbort = () => resolve(CANCELLED);
112
+ signal.addEventListener("abort", onAbort, { once: true });
113
+ }));
114
+ }
115
+ if (timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs >= 0) {
116
+ stops.push(new Promise((resolve) => {
117
+ timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
118
+ }));
119
+ }
120
+ let raw;
121
+ try {
122
+ // `Promise.race` subscribes to the check, so one that rejects after the
123
+ // race was decided is handled, not an unhandled rejection.
124
+ raw = await Promise.race([
125
+ Promise.resolve().then(() => checker.check({
126
+ claim: input.claim,
127
+ statement: input.statement,
128
+ proof: input.proof,
129
+ ...(signal !== undefined ? { signal } : {}),
130
+ })),
131
+ ...stops,
132
+ ]);
133
+ }
134
+ catch (err) {
135
+ return rejection(`the ${checker.id} checker failed: ${clip(errorMessage(err), 300)}`);
136
+ }
137
+ finally {
138
+ if (timer !== undefined)
139
+ clearTimeout(timer);
140
+ if (onAbort !== undefined)
141
+ signal?.removeEventListener("abort", onAbort);
142
+ }
143
+ if (raw === CANCELLED)
144
+ return rejection("the check was cancelled");
145
+ if (raw === TIMED_OUT) {
146
+ return rejection(`the ${checker.id} checker did not finish within ${timeoutMs} ms, so nothing was proved`);
147
+ }
148
+ const r = (typeof raw === "object" && raw !== null ? raw : {});
149
+ const reason = text(r["reason"]);
150
+ const counterexample = text(r["counterexample"]);
151
+ const duration = r["durationMs"];
152
+ const ok = r["ok"] === true;
153
+ return {
154
+ ok,
155
+ checker: checker.id,
156
+ assumptions: strings(r["assumptions"]),
157
+ axioms: strings(r["axioms"]),
158
+ output: typeof r["output"] === "string" ? r["output"] : "",
159
+ ...(reason !== undefined
160
+ ? { reason }
161
+ : ok
162
+ ? {}
163
+ : { reason: `the ${checker.id} checker did not accept the proof` }),
164
+ ...(counterexample !== undefined ? { counterexample } : {}),
165
+ durationMs: typeof duration === "number" && Number.isFinite(duration) && duration >= 0
166
+ ? Math.round(duration)
167
+ : elapsed(),
168
+ };
169
+ }
170
+ /**
171
+ * The artifact for one check — accepted or rejected alike: a rejected proof
172
+ * is persisted too, because proof pressure is signal (the `action_proof`
173
+ * rule from tool-plan), and `proof_rejected` in a bundle can then name the
174
+ * checker's reason. The `checker` is the id the model asked for, which is
175
+ * the registry's identity for the checker that ran.
176
+ */
177
+ export function toArtifact(args) {
178
+ const { result } = args;
179
+ const output = clipMiddle(result.output, OUTPUT_CHARS);
180
+ return {
181
+ version: 1,
182
+ id: args.id,
183
+ checker: args.checker.id,
184
+ claim: args.input.claim,
185
+ statement: args.input.statement,
186
+ proof: args.input.proof,
187
+ ok: result.ok,
188
+ assumptions: [...result.assumptions],
189
+ axioms: [...result.axioms],
190
+ output,
191
+ ...(result.counterexample !== undefined
192
+ ? { counterexample: clip(result.counterexample, DETAIL_CHARS) }
193
+ : {}),
194
+ ...(result.reason !== undefined ? { reason: clip(result.reason, DETAIL_CHARS) } : {}),
195
+ outputDigest: sha256Of(output),
196
+ checkedAt: args.checkedAt.toISOString(),
197
+ durationMs: result.durationMs,
198
+ };
199
+ }
200
+ /** The `proof_checked` payload: the trace event's fields, and the event-log line's whole payload. */
201
+ export function proofCheckedFields(artifact) {
202
+ return {
203
+ checker: artifact.checker,
204
+ ok: artifact.ok,
205
+ artifactId: artifact.id,
206
+ durationMs: artifact.durationMs ?? 0,
207
+ axioms: [...artifact.axioms],
208
+ };
209
+ }
210
+ /** The marker a ProofCheck report's lists end with: the artifact keeps every item. */
211
+ const keptInArtifact = (rest) => `… and ${rest} more (the artifact keeps them all)`;
212
+ /**
213
+ * As many of `items` as fit in `maxBytes`, each clipped to `itemChars`, in
214
+ * order, then `more(n)` counting the n left out — which the caller says
215
+ * where to find (the artifact keeps a ProofCheck's whole).
216
+ */
217
+ export function listed(items, maxBytes, more = keptInArtifact, itemChars = LISTED_CHARS) {
218
+ const shown = [];
219
+ // Room for the marker, so adding it never breaks the budget.
220
+ const reserve = jsonBytes(more(items.length)) + 1;
221
+ let used = 2; // the brackets
222
+ for (const item of items) {
223
+ const cell = clip(item, itemChars);
224
+ const cost = jsonBytes(cell) + (shown.length > 0 ? 1 : 0);
225
+ if (used + cost + (shown.length + 1 < items.length ? reserve : 0) > maxBytes)
226
+ break;
227
+ shown.push(cell);
228
+ used += cost;
229
+ }
230
+ const rest = items.length - shown.length;
231
+ return rest > 0 ? [...shown, more(rest)] : shown;
232
+ }
233
+ /**
234
+ * What the model reads back: the verdict, the artifact id and one
235
+ * instruction — how to cite it, or what to do instead — first, then why
236
+ * (reason, counterexample), what the proof rests on and the checker's
237
+ * output. The whole report fits in {@link REPORT_BYTES}, the output taking
238
+ * what the rest leaves; the artifact keeps everything whole.
239
+ */
240
+ export function checkReport(artifact) {
241
+ const next = artifact.ok
242
+ ? `cite it as [n]: proof:${artifact.id} — put [n] on the sentence that states this claim, with the same words, figures and polarity, or the gate reports claim_mismatch`
243
+ : `not proven, so this artifact cannot back a claim (the gate reports proof_rejected)${artifact.counterexample !== undefined ? "; the counterexample shows where it fails" : ""} — fix the statement or the proof and run ProofCheck again, or do not state the claim`;
244
+ const head = {
245
+ ok: artifact.ok,
246
+ id: artifact.id,
247
+ checker: artifact.checker,
248
+ next,
249
+ ...(artifact.reason !== undefined
250
+ ? { reason: fitText(artifact.reason, REPORT_REASON_BYTES) }
251
+ : {}),
252
+ ...(artifact.counterexample !== undefined
253
+ ? { counterexample: fitText(artifact.counterexample, REPORT_COUNTEREXAMPLE_BYTES) }
254
+ : {}),
255
+ };
256
+ const assumptions = listed(artifact.assumptions, REPORT_LIST_BYTES / 2);
257
+ const axioms = listed(artifact.axioms, REPORT_LIST_BYTES - jsonBytes(assumptions));
258
+ const rest = REPORT_BYTES - jsonBytes({ ...head, assumptions, axioms, output: "" });
259
+ return { ...head, assumptions, axioms, output: fitText(artifact.output, rest) };
260
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The proof checkers a harness proves with, and what `ProofCheckers` says
3
+ * about each (design §6.4, §7).
4
+ *
5
+ * The checkers come from `@crewhaus/proof-checker`'s `createDefaultCheckers`
6
+ * in the configured preference order (`formal.checkers`), with the builtin
7
+ * certificate checker always among them: it needs no sandbox, so a harness
8
+ * can always prove something. The sandbox checkers (`smt`, `lean4`) are
9
+ * always LISTED when configured — with no sandbox they say why in
10
+ * `available()` and refuse in `check()` — so the model sees what it could
11
+ * prove with and why it cannot right now, instead of a checker silently
12
+ * missing (the registry's own rule).
13
+ *
14
+ * The sandbox is created lazily, on the first call that needs the checkers,
15
+ * the way tool-code-execution creates its own: importing the package (the
16
+ * CLI imports every tool package) must not touch a container runtime.
17
+ */
18
+ import { type CheckerAvailability, type ProofChecker, type SandboxLike } from "@crewhaus/proof-checker";
19
+ import type { ProvabilityConfig } from "@crewhaus/provability";
20
+ /** The id of the checker that is always present. */
21
+ export declare const BUILTIN_ID = "builtin";
22
+ /**
23
+ * How long `ProofCheckers` waits on one checker's `available()`. The sandbox
24
+ * checkers bound their own probe (a cold image pull is slow, so 20 s), and
25
+ * remember the answer; this is the backstop for a plugin checker that never
26
+ * answers, so one hung probe cannot hang the listing.
27
+ */
28
+ export declare const AVAILABILITY_TIMEOUT_MS = 30000;
29
+ /** One checker as `ProofCheckers` reports it. */
30
+ export type CheckerListing = {
31
+ readonly id: string;
32
+ readonly available: boolean;
33
+ /** Why it cannot run here, when it cannot. */
34
+ readonly reason?: string;
35
+ /** The checker's own paragraph on its statement language. */
36
+ readonly language: string;
37
+ };
38
+ /**
39
+ * `checker.available()`, bounded and total: a throw, a malformed answer or
40
+ * no answer within `timeoutMs` reads as unavailable, with the reason.
41
+ */
42
+ export declare function probeChecker(checker: ProofChecker, timeoutMs?: number): Promise<CheckerAvailability>;
43
+ /** Every checker with its probed availability, in preference order. Probes run in parallel. */
44
+ export declare function listCheckers(checkers: ReadonlyArray<ProofChecker>, timeoutMs?: number): Promise<CheckerListing[]>;
45
+ /** `formal.checkers` in order, with the builtin appended when the config left it out. */
46
+ export declare function defaultCheckerIds(config: ProvabilityConfig): string[];
47
+ /** The container images the configured sandbox checkers run (what the sandbox must allow). */
48
+ export declare function sandboxImages(config: ProvabilityConfig): string[];
49
+ export type DefaultCheckerDeps = {
50
+ /** Whether a container sandbox may be used (CREWHAUS_SANDBOX, or the loop said one is wired). */
51
+ readonly sandboxAvailable: boolean;
52
+ /** Build the sandbox; `@crewhaus/sandbox`'s `createSandbox` in production. */
53
+ readonly createSandbox: (opts: {
54
+ readonly allowedImages: ReadonlyArray<string>;
55
+ }) => SandboxLike;
56
+ };
57
+ /**
58
+ * The default checkers for `config`: `formal.checkers` in order plus the
59
+ * builtin, with a sandbox for the sandbox checkers when one is available.
60
+ * Without one they are present and unavailable, saying why.
61
+ */
62
+ export declare function buildDefaultCheckers(config: ProvabilityConfig, deps: DefaultCheckerDeps): ProofChecker[];
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The proof checkers a harness proves with, and what `ProofCheckers` says
3
+ * about each (design §6.4, §7).
4
+ *
5
+ * The checkers come from `@crewhaus/proof-checker`'s `createDefaultCheckers`
6
+ * in the configured preference order (`formal.checkers`), with the builtin
7
+ * certificate checker always among them: it needs no sandbox, so a harness
8
+ * can always prove something. The sandbox checkers (`smt`, `lean4`) are
9
+ * always LISTED when configured — with no sandbox they say why in
10
+ * `available()` and refuse in `check()` — so the model sees what it could
11
+ * prove with and why it cannot right now, instead of a checker silently
12
+ * missing (the registry's own rule).
13
+ *
14
+ * The sandbox is created lazily, on the first call that needs the checkers,
15
+ * the way tool-code-execution creates its own: importing the package (the
16
+ * CLI imports every tool package) must not touch a container runtime.
17
+ */
18
+ import { DEFAULT_LEAN4_IMAGE, DEFAULT_SMT_IMAGE, createDefaultCheckers, } from "@crewhaus/proof-checker";
19
+ /** The id of the checker that is always present. */
20
+ export const BUILTIN_ID = "builtin";
21
+ /**
22
+ * How long `ProofCheckers` waits on one checker's `available()`. The sandbox
23
+ * checkers bound their own probe (a cold image pull is slow, so 20 s), and
24
+ * remember the answer; this is the backstop for a plugin checker that never
25
+ * answers, so one hung probe cannot hang the listing.
26
+ */
27
+ export const AVAILABILITY_TIMEOUT_MS = 30_000;
28
+ const message = (err) => (err instanceof Error ? err.message : String(err));
29
+ /**
30
+ * `checker.available()`, bounded and total: a throw, a malformed answer or
31
+ * no answer within `timeoutMs` reads as unavailable, with the reason.
32
+ */
33
+ export async function probeChecker(checker, timeoutMs = AVAILABILITY_TIMEOUT_MS) {
34
+ let timer;
35
+ const timeout = new Promise((resolve) => {
36
+ timer = setTimeout(() => resolve({
37
+ ok: false,
38
+ reason: `the ${checker.id} checker did not say whether it can run within ${timeoutMs} ms`,
39
+ }), timeoutMs);
40
+ });
41
+ try {
42
+ const answer = await Promise.race([
43
+ Promise.resolve()
44
+ .then(() => checker.available())
45
+ .catch((err) => ({
46
+ ok: false,
47
+ reason: `the ${checker.id} checker's availability probe failed: ${message(err)}`,
48
+ })),
49
+ timeout,
50
+ ]);
51
+ if (typeof answer !== "object" || answer === null || typeof answer.ok !== "boolean") {
52
+ return { ok: false, reason: `the ${checker.id} checker gave no availability answer` };
53
+ }
54
+ if (answer.ok)
55
+ return { ok: true };
56
+ return {
57
+ ok: false,
58
+ reason: typeof answer.reason === "string" && answer.reason.trim() !== ""
59
+ ? answer.reason
60
+ : `the ${checker.id} checker cannot run here`,
61
+ };
62
+ }
63
+ finally {
64
+ if (timer !== undefined)
65
+ clearTimeout(timer);
66
+ }
67
+ }
68
+ /** Every checker with its probed availability, in preference order. Probes run in parallel. */
69
+ export async function listCheckers(checkers, timeoutMs = AVAILABILITY_TIMEOUT_MS) {
70
+ return Promise.all(checkers.map(async (checker) => {
71
+ const availability = await probeChecker(checker, timeoutMs);
72
+ return {
73
+ id: checker.id,
74
+ available: availability.ok,
75
+ ...(availability.ok ? {} : { reason: availability.reason }),
76
+ language: typeof checker.language === "string" ? checker.language : "",
77
+ };
78
+ }));
79
+ }
80
+ /** `formal.checkers` in order, with the builtin appended when the config left it out. */
81
+ export function defaultCheckerIds(config) {
82
+ const ids = [...new Set(config.formal.checkers)];
83
+ return ids.includes(BUILTIN_ID) ? ids : [...ids, BUILTIN_ID];
84
+ }
85
+ /** The container images the configured sandbox checkers run (what the sandbox must allow). */
86
+ export function sandboxImages(config) {
87
+ const ids = defaultCheckerIds(config);
88
+ const images = [];
89
+ if (ids.includes("smt"))
90
+ images.push(DEFAULT_SMT_IMAGE);
91
+ // `formal.image` is the lean4 override (the Mathlib case) — proof-checker's
92
+ // reading of the spec key, which the gate's composition root shares.
93
+ if (ids.includes("lean4"))
94
+ images.push(config.formal.image ?? DEFAULT_LEAN4_IMAGE);
95
+ return images;
96
+ }
97
+ /**
98
+ * A sandbox that could not be built stands in as one whose every exec
99
+ * throws the reason: the sandbox checkers' probe then reports WHY ("could
100
+ * not be probed: CREWHAUS_SANDBOX=… is not a sandbox backend") instead of
101
+ * the generic "no sandbox is configured".
102
+ */
103
+ function brokenSandbox(err) {
104
+ const reason = message(err);
105
+ return {
106
+ backend: "unavailable",
107
+ async exec() {
108
+ throw new Error(`the sandbox could not be created: ${reason}`);
109
+ },
110
+ };
111
+ }
112
+ /**
113
+ * The default checkers for `config`: `formal.checkers` in order plus the
114
+ * builtin, with a sandbox for the sandbox checkers when one is available.
115
+ * Without one they are present and unavailable, saying why.
116
+ */
117
+ export function buildDefaultCheckers(config, deps) {
118
+ const ids = defaultCheckerIds(config);
119
+ const images = sandboxImages(config);
120
+ let sandbox;
121
+ if (images.length > 0 && deps.sandboxAvailable) {
122
+ try {
123
+ sandbox = deps.createSandbox({ allowedImages: images });
124
+ }
125
+ catch (err) {
126
+ sandbox = brokenSandbox(err);
127
+ }
128
+ }
129
+ return createDefaultCheckers({
130
+ ids,
131
+ timeoutMs: config.formal.timeoutMs,
132
+ forbid: config.formal.forbid,
133
+ ...(config.formal.image !== undefined ? { images: { lean4: config.formal.image } } : {}),
134
+ ...(sandbox !== undefined ? { sandbox } : {}),
135
+ });
136
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The configuration the proof tools run under when nobody hands them one.
3
+ *
4
+ * The composition root (`@crewhaus/evaluation-service`) builds the tools with
5
+ * the spec's lowered `provability:` block, so the gate and the tools agree on
6
+ * the floor, the checkers, the artifacts root and the evidence. The builtin
7
+ * instances — what a spec gets by listing `proofCheck` in `tools:` with no
8
+ * `provability:` block — have no block to read, so they run under the
9
+ * compiler's own defaults at the `grounded` floor: the values
10
+ * `lowerProvability` resolves for a bare `provability: grounded`
11
+ * (packages/compiler/src/index.ts, the IrProvability doc in
12
+ * packages/ir/src/index.ts). Copied rather than imported: a tool package
13
+ * does not import the compiler.
14
+ */
15
+ import { type ProvabilityConfig } from "@crewhaus/provability";
16
+ /** The floor the builtin instances preflight against (design §7). */
17
+ export declare const DEFAULT_FLOOR: "grounded";
18
+ /**
19
+ * `execution.allow`'s default, before the category grammar expands it
20
+ * (compiler `PROVABILITY_DEFAULT_EXECUTION_ALLOW`): the read-only computations
21
+ * and gates, less the one destructive verify tool (design §6.3).
22
+ * `apps/cli/src/provability-default-allow.test.ts` holds the two copies equal.
23
+ */
24
+ export declare const DEFAULT_EXECUTION_SELECTORS: ReadonlyArray<string>;
25
+ /** The spec name proofs are filed under when neither the caller nor the run names one. */
26
+ export declare const DEFAULT_SPEC_NAME = "default";
27
+ /**
28
+ * The lowered defaults at the `grounded` floor. Built on call, not at import:
29
+ * the expansion walks the category registry, and the CLI imports every tool
30
+ * package whether a spec lists it or not.
31
+ */
32
+ export declare function defaultProvabilityConfig(): ProvabilityConfig;
33
+ /**
34
+ * A config handed in by a caller, checked once at construction: a tool built
35
+ * on a block with no valid floor would throw on every call instead, far from
36
+ * the mistake. Only the floor is checked — `verifyDocument` reads the rest
37
+ * and refuses what it cannot use, and the lowering already resolved it.
38
+ */
39
+ export declare function assertConfig(config: ProvabilityConfig): ProvabilityConfig;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The configuration the proof tools run under when nobody hands them one.
3
+ *
4
+ * The composition root (`@crewhaus/evaluation-service`) builds the tools with
5
+ * the spec's lowered `provability:` block, so the gate and the tools agree on
6
+ * the floor, the checkers, the artifacts root and the evidence. The builtin
7
+ * instances — what a spec gets by listing `proofCheck` in `tools:` with no
8
+ * `provability:` block — have no block to read, so they run under the
9
+ * compiler's own defaults at the `grounded` floor: the values
10
+ * `lowerProvability` resolves for a bare `provability: grounded`
11
+ * (packages/compiler/src/index.ts, the IrProvability doc in
12
+ * packages/ir/src/index.ts). Copied rather than imported: a tool package
13
+ * does not import the compiler.
14
+ */
15
+ import { ProvabilityError, isProvabilityLevel, } from "@crewhaus/provability";
16
+ import { expandToolSelectors } from "@crewhaus/tool-categories";
17
+ /** The floor the builtin instances preflight against (design §7). */
18
+ export const DEFAULT_FLOOR = "grounded";
19
+ /**
20
+ * `execution.allow`'s default, before the category grammar expands it
21
+ * (compiler `PROVABILITY_DEFAULT_EXECUTION_ALLOW`): the read-only computations
22
+ * and gates, less the one destructive verify tool (design §6.3).
23
+ * `apps/cli/src/provability-default-allow.test.ts` holds the two copies equal.
24
+ */
25
+ export const DEFAULT_EXECUTION_SELECTORS = [
26
+ "all-compute",
27
+ "all-verify",
28
+ "-goldenUpdate",
29
+ ];
30
+ /** The spec name proofs are filed under when neither the caller nor the run names one. */
31
+ export const DEFAULT_SPEC_NAME = "default";
32
+ /**
33
+ * The lowered defaults at the `grounded` floor. Built on call, not at import:
34
+ * the expansion walks the category registry, and the CLI imports every tool
35
+ * package whether a spec lists it or not.
36
+ */
37
+ export function defaultProvabilityConfig() {
38
+ return {
39
+ level: DEFAULT_FLOOR,
40
+ onFail: "retry",
41
+ maxRetries: 1,
42
+ threshold: 1,
43
+ scope: "session",
44
+ assumptions: "allow",
45
+ render: "footer",
46
+ sources: { allow: [], deny: [], bibliographic: false, resolve: "off" },
47
+ grounded: { coverage: 0.8, windowTokens: 60, independent: 1 },
48
+ execution: {
49
+ allow: [...expandToolSelectors(DEFAULT_EXECUTION_SELECTORS).tools],
50
+ replay: true,
51
+ maxReplays: 20,
52
+ },
53
+ formal: {
54
+ checkers: ["builtin", "smt"],
55
+ forbid: ["sorry", "axiom", "native_decide"],
56
+ timeoutMs: 30_000,
57
+ },
58
+ };
59
+ }
60
+ /**
61
+ * A config handed in by a caller, checked once at construction: a tool built
62
+ * on a block with no valid floor would throw on every call instead, far from
63
+ * the mistake. Only the floor is checked — `verifyDocument` reads the rest
64
+ * and refuses what it cannot use, and the lowering already resolved it.
65
+ */
66
+ export function assertConfig(config) {
67
+ if (!isProvabilityLevel(config?.level)) {
68
+ throw new ProvabilityError("config", `tool-proof: config.level must be one of cited, grounded, checked, proven (got ${JSON.stringify(config?.level)})`);
69
+ }
70
+ return config;
71
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What a proof tool reads off the call's `ToolExecuteContext`: the run's
3
+ * session, its trace bus, its event log and where its session logs live.
4
+ *
5
+ * The runtime threads its `RunContext` through `ctx.runContext` where it can
6
+ * and always through the opaque bridge (`ctx.bridge.runContext`, the field
7
+ * tool-code-execution's `resolveEventBus` and tool-plan's `identityOf` read).
8
+ * The bridge also carries the run's `eventLog`, its `sessionRootDir` and
9
+ * whether a sandbox backend is wired (`RuntimeBridge`,
10
+ * packages/agent-context-isolation). All of it is read STRUCTURALLY, so this
11
+ * package imports no runtime wiring, and every field is optional: a bare
12
+ * unit call (a test, a CLI verb) has none of it, and the tools then work
13
+ * against the defaults rather than failing.
14
+ */
15
+ import type { ToolExecuteContext } from "@crewhaus/tool-catalog";
16
+ import type { TraceEventBus } from "@crewhaus/trace-event-bus";
17
+ /** The slice of a `RunContext` the tools read. */
18
+ export type RunContextLike = {
19
+ readonly sessionId?: unknown;
20
+ readonly turnNumber?: unknown;
21
+ readonly eventBus?: TraceEventBus;
22
+ readonly logger?: {
23
+ warn(msg: string, fields?: Record<string, unknown>): void;
24
+ };
25
+ /** Not on today's `RunContext`; read when a runtime carries it (design §7). */
26
+ readonly specName?: unknown;
27
+ };
28
+ /** The slice of an event log the tools append to. */
29
+ export type EventLogLike = {
30
+ append(event: {
31
+ readonly kind: "proof_checked";
32
+ readonly payload: unknown;
33
+ }): Promise<void>;
34
+ };
35
+ /** The slice of `RuntimeBridge` the tools read. */
36
+ export type BridgeLike = {
37
+ readonly runContext?: RunContextLike;
38
+ readonly eventLog?: EventLogLike;
39
+ readonly sessionRootDir?: unknown;
40
+ readonly sandboxAvailable?: unknown;
41
+ };
42
+ /** The run's context: `ctx.runContext` first, the bridge's otherwise. */
43
+ export declare function runContextOf(ctx: ToolExecuteContext | undefined): RunContextLike | undefined;
44
+ /** The run's session id, when it has a well-formed one. */
45
+ export declare function sessionIdOf(ctx: ToolExecuteContext | undefined): string | undefined;
46
+ /** The run's trace bus, or undefined off the loop (publishing is then a no-op). */
47
+ export declare function eventBusOf(ctx: ToolExecuteContext | undefined): TraceEventBus | undefined;
48
+ /** The run's session event log, when the runtime handed one over. */
49
+ export declare function eventLogOf(ctx: ToolExecuteContext | undefined): EventLogLike | undefined;
50
+ /** Where the run's session logs live, when the runtime said. */
51
+ export declare function sessionRootOf(ctx: ToolExecuteContext | undefined): string | undefined;
52
+ /** Whether the loop was told a sandbox backend is wired (`runChatLoop({ sandboxAvailable })`). */
53
+ export declare function bridgeSandboxAvailable(ctx: ToolExecuteContext | undefined): boolean;
54
+ /** The spec name the run carries, when it carries a valid one. */
55
+ export declare function specNameOf(ctx: ToolExecuteContext | undefined): string | undefined;
56
+ /** Log a warning on the run's logger; silent off the loop. Never throws. */
57
+ export declare function warn(ctx: ToolExecuteContext | undefined, msg: string, fields?: Record<string, unknown>): void;