@codacy/verity-cli 0.34.0-experimental.37a6dcf → 0.34.0-experimental.421a631
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -77
- package/bin/verity.js +78 -138
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -64,78 +64,6 @@ installs are untouched.
|
|
|
64
64
|
which is the ordinary case for someone whose first Verity install *is* the
|
|
65
65
|
plugin.
|
|
66
66
|
|
|
67
|
-
## [0.34.0] — 2026-09-30
|
|
68
|
-
|
|
69
|
-
**Formal verification at push: Verity can now prove a bug is real before it
|
|
70
|
-
blocks — and the code map and the knowledge loop tell the reviewer more about
|
|
71
|
-
the code around a change.**
|
|
72
|
-
|
|
73
|
-
### 🧮 Formal verification, opt-in
|
|
74
|
-
|
|
75
|
-
A third review at `git push`, next to the one-shot and the agent. It picks the
|
|
76
|
-
riskiest function the push changed, has the Verity service write a small Lean 4
|
|
77
|
-
model of it, checks the model on your machine, and replays any counterexample
|
|
78
|
-
on the real code in an isolated container with the network off. **Only a
|
|
79
|
-
reproduced bug blocks;** a counterexample that does not reproduce on the real
|
|
80
|
-
code is reported, never enforced. Most pushes get no model at all.
|
|
81
|
-
|
|
82
|
-
- **Off until you say yes.** `verity init` asks once, on a terminal, with "no"
|
|
83
|
-
as the default, and remembers either answer. `--yes` and headless runs are not
|
|
84
|
-
asked and never probe for a toolchain. `verity config formal push` turns it on
|
|
85
|
-
for a checkout, `verity config formal off` turns it off. It needs you to be
|
|
86
|
-
signed in: ranking, model writing and replay run on the Verity service. The
|
|
87
|
-
function's source is sent for the replay; no code is stored.
|
|
88
|
-
- **`verity lean install | status | remove`** manages the pinned official Lean 4
|
|
89
|
-
release under `~/.verity/lean` (≈800 MB download, 2.7 GB on disk). The
|
|
90
|
-
download is checksum-verified, abandoned after 60 s with nothing received,
|
|
91
|
-
and refused up front when the disk is short. An elan install is used when it
|
|
92
|
-
already has the pinned toolchain; Verity never makes elan download one.
|
|
93
|
-
- **On demand:** `verity prove candidates | generate | check | replay | auto`,
|
|
94
|
-
and the `/verity-prove <worry>` skill, where the words after the command are
|
|
95
|
-
what you want checked. Under the plugin, `/verity:verity-prove` arrives with
|
|
96
|
-
the plugin's next release.
|
|
97
|
-
- **A finding holds until the code changes.** Pushing the same bytes again
|
|
98
|
-
re-blocks without a new model. `verity prove waive <file>` accepts the risk
|
|
99
|
-
for the file's current bytes, and voids when they change.
|
|
100
|
-
- **Yours, never the repository's.** A `.verity/config.json` that git tracks,
|
|
101
|
-
or that is reached through a symlink, cannot switch formal verification on —
|
|
102
|
-
`verity doctor`, `verity config formal` and `verity init` say why it is off.
|
|
103
|
-
Findings and spend records a repository ships are ignored, and Lean never runs
|
|
104
|
-
on a `.verity/proofs/` workspace the repository supplies.
|
|
105
|
-
- **Admins can switch it off** per project, organization or globally with the
|
|
106
|
-
`formal` flag in the dashboard; the push then reports `disabled-by-server`.
|
|
107
|
-
|
|
108
|
-
### 🗺️ The code map
|
|
109
|
-
|
|
110
|
-
The CLI now parses TypeScript, TSX, JavaScript, Python, Go and Java with bundled
|
|
111
|
-
tree-sitter grammars (about 6 MB added to the package).
|
|
112
|
-
|
|
113
|
-
- **The reviewer sees what else touches the same state.** At commit and push,
|
|
114
|
-
the review's context names the other functions that write the state a change
|
|
115
|
-
writes, and the agent reviewer can ask for a function's callers and
|
|
116
|
-
definitions. Formal verification models the exact function, not a guess at
|
|
117
|
-
its extent.
|
|
118
|
-
- **Reads use the bytes under review:** at a push, the pushed commit; at a
|
|
119
|
-
commit, the staged files — not whatever is in the working tree.
|
|
120
|
-
- **Paths the project denies are never shown** by the code tools or in the
|
|
121
|
-
code-map context.
|
|
122
|
-
|
|
123
|
-
### 🧠 The knowledge loop
|
|
124
|
-
|
|
125
|
-
- **The reviewer says what it did with each node it was shown** — applied,
|
|
126
|
-
violated, irrelevant or unclear, each tied to a line in the change — and
|
|
127
|
-
ranking learns from it which nodes help. One scorer now chooses the nodes for
|
|
128
|
-
both the review and your agent's prompt.
|
|
129
|
-
- **A known rule said back to the agent.** When a change breaks a rule the
|
|
130
|
-
project has already learned, Verity can tell the agent which one. This ships
|
|
131
|
-
in shadow: the decision is made and recorded, and nothing is sent.
|
|
132
|
-
- **Fewer, better nodes.** The extractor's rubric was rewritten from 143
|
|
133
|
-
human-graded nodes; it no longer records what the code already says, and it
|
|
134
|
-
checks each new node against the nearest existing ones before adding it.
|
|
135
|
-
- **A failed diff in `verity memory` sync no longer drops a node edit you made.**
|
|
136
|
-
- **A failed knowledge lookup no longer fails a review.** The review runs
|
|
137
|
-
without knowledge and records that it did.
|
|
138
|
-
|
|
139
67
|
### 🎯 `/verity:init` does everything `verity init` does, in the session
|
|
140
68
|
|
|
141
69
|
Under the plugin, setup used to stop short of the terminal flow: `/verity:init`
|
|
@@ -161,11 +89,6 @@ sign-in meant leaving Claude Code for a terminal.
|
|
|
161
89
|
- **`verity telemetry install|uninstall` update `.verity/setup.json`**, so a
|
|
162
90
|
request deferred until sign-in stops being reported as still to do.
|
|
163
91
|
|
|
164
|
-
### 🔧 Fixed
|
|
165
|
-
|
|
166
|
-
- **A swapped `.verity` directory cannot redirect a write.** File writes check
|
|
167
|
-
the file they opened is the one they meant to write, after opening it.
|
|
168
|
-
|
|
169
92
|
## [0.33.3] — 2026-09-24
|
|
170
93
|
|
|
171
94
|
**The knowledge seed runs once per project, not once per checkout.**
|
package/bin/verity.js
CHANGED
|
@@ -17221,7 +17221,7 @@ function readConfigFile(path) {
|
|
|
17221
17221
|
return {
|
|
17222
17222
|
git_moments: [...new Set(moments)],
|
|
17223
17223
|
...source2 ? { git_moments_source: source2 } : {},
|
|
17224
|
-
...raw.formal === "push"
|
|
17224
|
+
...raw.formal === "push" ? { formal: "push" } : {}
|
|
17225
17225
|
};
|
|
17226
17226
|
} catch {
|
|
17227
17227
|
return DEFAULTS;
|
|
@@ -17250,10 +17250,10 @@ function readProjectConfigRaw() {
|
|
|
17250
17250
|
function readProjectConfig() {
|
|
17251
17251
|
const cfg = readProjectConfigRaw();
|
|
17252
17252
|
if (cfg.formal !== "push" || !cfg.path) return cfg;
|
|
17253
|
-
const
|
|
17254
|
-
if (!
|
|
17253
|
+
const refused = formalRefusal(cfg.path);
|
|
17254
|
+
if (!refused) return cfg;
|
|
17255
17255
|
const { formal: _dropped, ...rest } = cfg;
|
|
17256
|
-
return { ...rest, formal_refused:
|
|
17256
|
+
return { ...rest, formal_refused: refused };
|
|
17257
17257
|
}
|
|
17258
17258
|
function formalRefusalMessage(reason) {
|
|
17259
17259
|
switch (reason) {
|
|
@@ -24152,7 +24152,7 @@ function registerConfigCommands(program2) {
|
|
|
24152
24152
|
config.command("formal [state]").description('Get or set formal verification at push: "push" or "off" (needs Lean \u2014 verity lean status)').action((state) => {
|
|
24153
24153
|
if (state === void 0) {
|
|
24154
24154
|
const cfg = readProjectConfig();
|
|
24155
|
-
process.stdout.write((cfg.formal
|
|
24155
|
+
process.stdout.write((cfg.formal ?? "off") + "\n");
|
|
24156
24156
|
if (cfg.formal_refused) printWarn(formalRefusalMessage(cfg.formal_refused));
|
|
24157
24157
|
return;
|
|
24158
24158
|
}
|
|
@@ -24160,10 +24160,10 @@ function registerConfigCommands(program2) {
|
|
|
24160
24160
|
printError(`Unrecognised state: ${state}. Use "push" or "off".`);
|
|
24161
24161
|
process.exit(1);
|
|
24162
24162
|
}
|
|
24163
|
-
writeProjectConfig({ formal: state === "push" ? "push" :
|
|
24164
|
-
const
|
|
24165
|
-
if (
|
|
24166
|
-
printWarn(`Formal verification at push: still off \u2014 ${formalRefusalMessage(
|
|
24163
|
+
writeProjectConfig({ formal: state === "push" ? "push" : void 0 });
|
|
24164
|
+
const refused = state === "push" ? readProjectConfig().formal_refused : void 0;
|
|
24165
|
+
if (refused) {
|
|
24166
|
+
printWarn(`Formal verification at push: still off \u2014 ${formalRefusalMessage(refused)}`);
|
|
24167
24167
|
return;
|
|
24168
24168
|
}
|
|
24169
24169
|
printInfo(state === "push" ? "Formal verification at push: on for this checkout (Lean must be installed \u2014 verity lean status)" : "Formal verification at push: off");
|
|
@@ -27073,7 +27073,7 @@ function channelSilence(input) {
|
|
|
27073
27073
|
// src/lib/cli-version.ts
|
|
27074
27074
|
function cliVersion() {
|
|
27075
27075
|
try {
|
|
27076
|
-
return true ? "0.34.0-experimental.
|
|
27076
|
+
return true ? "0.34.0-experimental.421a631" : "dev";
|
|
27077
27077
|
} catch {
|
|
27078
27078
|
return "dev";
|
|
27079
27079
|
}
|
|
@@ -33869,11 +33869,6 @@ async function rankViaServer(candidates2, ctx, auth2) {
|
|
|
33869
33869
|
cmd: "prove-rank"
|
|
33870
33870
|
});
|
|
33871
33871
|
if (!res.ok) return { ok: false, ranker: "luna", error: res.error, ranked: fallback };
|
|
33872
|
-
const off = res.data;
|
|
33873
|
-
if (off?.enabled === false) {
|
|
33874
|
-
const why = typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled";
|
|
33875
|
-
return { ok: false, ranker: "luna", error: `formal is off for this project (${why})`, ranked: fallback, disabled: why };
|
|
33876
|
-
}
|
|
33877
33872
|
const items = Array.isArray(res.data?.items) ? res.data.items : [];
|
|
33878
33873
|
return { ok: true, ranker: "luna", ranked: applyAnnotations(candidates2, items), costUsd: typeof res.data?.cost_usd === "number" ? res.data.cost_usd : null };
|
|
33879
33874
|
}
|
|
@@ -34598,8 +34593,6 @@ async function generateModel(target, opts) {
|
|
|
34598
34593
|
});
|
|
34599
34594
|
if (!res.ok) return { ok: false, error: res.error };
|
|
34600
34595
|
const d = res.data ?? {};
|
|
34601
|
-
const off = d;
|
|
34602
|
-
if (off.enabled === false) return { ok: false, error: `formal is off for this project (${typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled"})` };
|
|
34603
34596
|
if (d.error && typeof d.error === "object") return { ok: false, error: `${d.error.code ?? "SERVICE_ERROR"}: ${d.error.message ?? "the service failed"}` };
|
|
34604
34597
|
const usage2 = d.usage && typeof d.usage === "object" ? { inputTokens: Number(d.usage.inputTokens ?? 0), outputTokens: Number(d.usage.outputTokens ?? 0) } : null;
|
|
34605
34598
|
return acceptAnswer(d, usage2, typeof d.cost_usd === "number" ? d.cost_usd : null);
|
|
@@ -34779,7 +34772,8 @@ Rules:
|
|
|
34779
34772
|
5. Write the harness as ONE file and run it in the SAME shell call that writes it (a heredoc followed by the run). TypeScript: node --experimental-strip-types harness.ts (detect runtimes only if that fails: node --version, python3 --version); if strip-types is unavailable, remove type annotations ONLY on lines that are not part of the function under test's logic, and say so. Python: python3 harness.py.
|
|
34780
34773
|
6. Nothing you run can reach the network. Do not try.
|
|
34781
34774
|
7. Keep it under about eight shell calls. Output tokens are time.
|
|
34782
|
-
8. Answer with the JSON object only: verdict (reproduced | diverged | unreplayable), reason (2-4 sentences: what you ran, what the real code did, and how that compares to the trace's final state), harness_files (every file you wrote: path and its line count \u2014 do NOT paste contents, your shell calls already carry them), executed_output (the last relevant stdout, at most 2000 characters).
|
|
34775
|
+
8. Answer with the JSON object only: verdict (reproduced | diverged | unreplayable), reason (2-4 sentences: what you ran, what the real code did, and how that compares to the trace's final state), harness_files (every file you wrote: path and its line count \u2014 do NOT paste contents, your shell calls already carry them), executed_output (the last relevant stdout, at most 2000 characters), impact (one word, below).
|
|
34776
|
+
9. impact \u2014 AFTER the verdict, and never a reason to change it: what the violation you ran costs a user of this code, the most serious one the run showed. money: a balance, charge, refund, price or stock is wrong. access: someone gets access they should not have, or an auth check is bypassed. data: a user's records are lost, corrupted or wrongly duplicated. external: an effect outside the system happens twice or never (a charge, an email, a shipment, a webhook). count: a counter, metric or statistic is off. cache: a cached or derived value is stale until it refreshes. internal: the program's own bookkeeping is off, with no effect a user would see. none: the verdict is not reproduced.
|
|
34783
34777
|
|
|
34784
34778
|
"reproduced" means the real code, driven by the trace, reached the property violation the model describes. "diverged" means it did not \u2014 the model is wrong about the code, which is a useful answer. "unreplayable" means you could not run the real function faithfully; say why.`;
|
|
34785
34779
|
var REPLAY_SCHEMA = {
|
|
@@ -34797,9 +34791,10 @@ var REPLAY_SCHEMA = {
|
|
|
34797
34791
|
required: ["path", "lines"]
|
|
34798
34792
|
}
|
|
34799
34793
|
},
|
|
34800
|
-
executed_output: { type: "string" }
|
|
34794
|
+
executed_output: { type: "string" },
|
|
34795
|
+
impact: { type: "string", enum: ["money", "access", "data", "external", "count", "cache", "internal", "none"] }
|
|
34801
34796
|
},
|
|
34802
|
-
required: ["verdict", "reason", "harness_files", "executed_output"]
|
|
34797
|
+
required: ["verdict", "reason", "harness_files", "executed_output", "impact"]
|
|
34803
34798
|
};
|
|
34804
34799
|
function renderBriefInput(brief) {
|
|
34805
34800
|
const p = brief.property;
|
|
@@ -34826,6 +34821,8 @@ function renderBriefInput(brief) {
|
|
|
34826
34821
|
}
|
|
34827
34822
|
return parts2.join("\n");
|
|
34828
34823
|
}
|
|
34824
|
+
var IMPACTS = ["money", "access", "data", "external", "count", "cache", "internal", "none"];
|
|
34825
|
+
var isImpact = (v) => typeof v === "string" && IMPACTS.includes(v);
|
|
34829
34826
|
function stringsIn(value, out2 = [], depth = 0) {
|
|
34830
34827
|
if (depth > 8) return out2;
|
|
34831
34828
|
if (typeof value === "string") out2.push(value);
|
|
@@ -34867,7 +34864,8 @@ function parseReplayOutput(output) {
|
|
|
34867
34864
|
verdict,
|
|
34868
34865
|
reason: typeof o.reason === "string" ? o.reason.slice(0, 2e3) : "",
|
|
34869
34866
|
harness_files: Array.isArray(o.harness_files) ? o.harness_files.filter((f) => typeof f.path === "string").map((f) => ({ path: String(f.path).slice(0, 200), lines: Number.isInteger(f.lines) ? f.lines : 0 })) : [],
|
|
34870
|
-
executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : ""
|
|
34867
|
+
executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : "",
|
|
34868
|
+
impact: isImpact(o.impact) ? o.impact : "none"
|
|
34871
34869
|
};
|
|
34872
34870
|
}
|
|
34873
34871
|
} catch {
|
|
@@ -34875,10 +34873,10 @@ function parseReplayOutput(output) {
|
|
|
34875
34873
|
}
|
|
34876
34874
|
return { claim, shellCalls, executedCalls, shellTexts, outputTexts };
|
|
34877
34875
|
}
|
|
34878
|
-
var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b
|
|
34876
|
+
var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b/;
|
|
34879
34877
|
function printedResult(outputTexts) {
|
|
34880
34878
|
let last = null;
|
|
34881
|
-
for (const t of outputTexts) for (const m of t.matchAll(RESULT_LINE)) last = m[1];
|
|
34879
|
+
for (const t of outputTexts) for (const m of t.matchAll(new RegExp(RESULT_LINE.source, "g"))) last = m[1];
|
|
34882
34880
|
return last;
|
|
34883
34881
|
}
|
|
34884
34882
|
var RESULT_MISSING = "the harness never printed REPLAY_RESULT=reproduced on its own stdout \u2014 the verdict was the model's word, not the run's";
|
|
@@ -35068,6 +35066,7 @@ async function runReplayInContainer(brief, opts) {
|
|
|
35068
35066
|
...judged,
|
|
35069
35067
|
executedCalls,
|
|
35070
35068
|
shellCalls,
|
|
35069
|
+
...judged.verdict === "reproduced" && parsed.claim ? { impact: parsed.claim.impact } : {},
|
|
35071
35070
|
executedOutput: parsed.claim?.executed_output ?? "",
|
|
35072
35071
|
harnessFiles: parsed.claim?.harness_files ?? [],
|
|
35073
35072
|
usage,
|
|
@@ -35114,11 +35113,6 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
|
|
|
35114
35113
|
return { claimed: null, verdict: "unverified", reason: "", downgrade: res.error, executedCalls: 0, shellCalls: 0, verbatim: none, executedOutput: "", harnessFiles: [], usage: null, durationMs: Date.now() - started, containerId: null, error: res.error };
|
|
35115
35114
|
}
|
|
35116
35115
|
const d = res.data ?? {};
|
|
35117
|
-
if (d.enabled === false) {
|
|
35118
|
-
const why = d.reason;
|
|
35119
|
-
const msg = `formal is off for this project (${typeof why === "string" ? why.slice(0, 40) : "disabled"})`;
|
|
35120
|
-
return { claimed: null, verdict: "unverified", reason: "", downgrade: msg, executedCalls: 0, shellCalls: 0, verbatim: none, executedOutput: "", harnessFiles: [], usage: null, durationMs: Date.now() - started, containerId: null, error: msg };
|
|
35121
|
-
}
|
|
35122
35116
|
if (d.error && typeof d.error === "object") {
|
|
35123
35117
|
const e = d.error;
|
|
35124
35118
|
const msg = `${e.code ?? "SERVICE_ERROR"}: ${e.message ?? "the service failed"}`;
|
|
@@ -35131,6 +35125,7 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
|
|
|
35131
35125
|
verdict: verdict === "reproduced" || verdict === "diverged" || verdict === "unreplayable" ? verdict : "unverified",
|
|
35132
35126
|
reason: typeof d.reason === "string" ? d.reason : "",
|
|
35133
35127
|
downgrade: typeof d.downgrade === "string" ? d.downgrade : null,
|
|
35128
|
+
...isImpact(d.impact) ? { impact: d.impact } : {},
|
|
35134
35129
|
executedCalls: Number(d.executed_calls ?? 0),
|
|
35135
35130
|
shellCalls: Number(d.shell_calls ?? 0),
|
|
35136
35131
|
verbatim: { covered: Number(v.covered ?? 0), total: Number(v.total ?? 0), ratio: Number(v.ratio ?? 0), basis: v.basis === "symbol" || v.basis === "file" ? v.basis : "none" },
|
|
@@ -35915,10 +35910,9 @@ function verityLeanHome(env = process.env) {
|
|
|
35915
35910
|
function findToolchain(env = process.env) {
|
|
35916
35911
|
const managedBin = (0, import_node_path43.join)(verityLeanHome(env), LEAN_TOOLCHAIN.replace(/^leanprover\/lean4:/, ""), "bin");
|
|
35917
35912
|
const elanBin = (0, import_node_path43.join)(env.HOME ?? (0, import_node_os9.homedir)(), ".elan", "bin");
|
|
35918
|
-
const
|
|
35919
|
-
const userPath = (env.PATH ?? "").split(import_node_path43.delimiter).filter((d) => d && (elanHas || (0, import_node_path43.resolve)(d) !== (0, import_node_path43.resolve)(elanBin)));
|
|
35920
|
-
const path = [managedBin, ...elanHas ? [elanBin] : [], ...userPath].join(import_node_path43.delimiter);
|
|
35913
|
+
const path = [managedBin, elanBin, env.PATH ?? ""].filter(Boolean).join(import_node_path43.delimiter);
|
|
35921
35914
|
const merged = { ...env, PATH: path, ELAN_TOOLCHAIN: env.ELAN_TOOLCHAIN ?? LEAN_TOOLCHAIN };
|
|
35915
|
+
const elanHas = (0, import_node_fs58.existsSync)((0, import_node_path43.join)(env.HOME ?? (0, import_node_os9.homedir)(), ".elan", "toolchains", LEAN_TOOLCHAIN.replace("/", "--").replace(":", "---")));
|
|
35922
35916
|
const exe = process.platform === "win32" ? "lake.exe" : "lake";
|
|
35923
35917
|
const candidates2 = [(0, import_node_path43.join)(managedBin, exe), ...elanHas ? [(0, import_node_path43.join)(elanBin, exe)] : [], exe];
|
|
35924
35918
|
for (const candidate of candidates2) {
|
|
@@ -35995,35 +35989,10 @@ function runAsync(tc, cwd, args2, timeoutMs) {
|
|
|
35995
35989
|
}
|
|
35996
35990
|
var DEFAULT_BUILD_TIMEOUT_MS = 10 * 6e4;
|
|
35997
35991
|
var DEFAULT_CHECK_TIMEOUT_MS = 12e4;
|
|
35998
|
-
function proofsWorkspaceProblem(root) {
|
|
35999
|
-
for (const rel of [".verity", PROOFS_DIR, `${PROOFS_DIR}/.lake`]) {
|
|
36000
|
-
let st;
|
|
36001
|
-
try {
|
|
36002
|
-
st = (0, import_node_fs58.lstatSync)((0, import_node_path43.join)(root, rel));
|
|
36003
|
-
} catch {
|
|
36004
|
-
break;
|
|
36005
|
-
}
|
|
36006
|
-
if (st.isSymbolicLink()) return `${rel} is a symlink; Lean is not run through it`;
|
|
36007
|
-
}
|
|
36008
|
-
const r = runGitSync(root, ["ls-files", "-z", "--", PROOFS_DIR], { timeoutMs: 5e3 });
|
|
36009
|
-
if (!r.ok) return `could not confirm with git that ${PROOFS_DIR} holds no committed files; Lean is not run`;
|
|
36010
|
-
const tracked = r.stdout.split("\0").filter(Boolean);
|
|
36011
|
-
if (tracked.length > 0) {
|
|
36012
|
-
return `${tracked.length} file(s) under ${PROOFS_DIR} are tracked in git (e.g. ${tracked[0]}); Lean is not run on a workspace the repository supplies \u2014 untrack them with: git rm -r --cached ${PROOFS_DIR}`;
|
|
36013
|
-
}
|
|
36014
|
-
return null;
|
|
36015
|
-
}
|
|
36016
|
-
function refused(problem) {
|
|
36017
|
-
return { ok: false, stdout: "", stderr: problem, durationMs: 0, timedOut: false };
|
|
36018
|
-
}
|
|
36019
35992
|
function buildLibrary(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
|
|
36020
|
-
const problem = proofsWorkspaceProblem(root);
|
|
36021
|
-
if (problem) return refused(problem);
|
|
36022
35993
|
return run2(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
|
|
36023
35994
|
}
|
|
36024
|
-
|
|
36025
|
-
const problem = proofsWorkspaceProblem(root);
|
|
36026
|
-
if (problem) return refused(problem);
|
|
35995
|
+
function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
|
|
36027
35996
|
return runAsync(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
|
|
36028
35997
|
}
|
|
36029
35998
|
function isModelInRoot(modelsAbs, rel) {
|
|
@@ -36050,15 +36019,13 @@ function trimDiagnostics(text) {
|
|
|
36050
36019
|
function checkModel(tc, root, modelFile, opts = {}) {
|
|
36051
36020
|
const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
|
|
36052
36021
|
const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
|
|
36053
|
-
const
|
|
36054
|
-
const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36022
|
+
const res = run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36055
36023
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36056
36024
|
}
|
|
36057
36025
|
async function checkModelAsync(tc, root, modelFile, opts = {}) {
|
|
36058
36026
|
const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
|
|
36059
36027
|
const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
|
|
36060
|
-
const
|
|
36061
|
-
const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36028
|
+
const res = await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36062
36029
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36063
36030
|
}
|
|
36064
36031
|
function toReport(res, root, modelFile, timeoutMs) {
|
|
@@ -36113,18 +36080,33 @@ function writeResult(root, report) {
|
|
|
36113
36080
|
function isCrashTrace(trace) {
|
|
36114
36081
|
return trace.some((s) => /\b(crash\w*|kill\w*|restart\w*|interrupt\w*|power|abort\w*|process (dies|exit\w*)|lost (reply|response))\b/i.test(s));
|
|
36115
36082
|
}
|
|
36083
|
+
var SEVERITY_BY_IMPACT = {
|
|
36084
|
+
money: "high",
|
|
36085
|
+
access: "high",
|
|
36086
|
+
data: "high",
|
|
36087
|
+
external: "high",
|
|
36088
|
+
count: "medium",
|
|
36089
|
+
cache: "medium",
|
|
36090
|
+
internal: "medium",
|
|
36091
|
+
// A reproduced finding always has a real code; 'none' here is a malformed answer, so it blocks.
|
|
36092
|
+
none: "high"
|
|
36093
|
+
};
|
|
36094
|
+
function formalSeverity(f) {
|
|
36095
|
+
return f.impact && SEVERITY_BY_IMPACT[f.impact] || "high";
|
|
36096
|
+
}
|
|
36116
36097
|
function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
36117
|
-
if (!outcome) return { blocking: 0 };
|
|
36098
|
+
if (!outcome) return { blocking: 0, warning: 0 };
|
|
36118
36099
|
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
36119
36100
|
const pushed = new Set(pushedFiles.map(norm2));
|
|
36120
|
-
const
|
|
36121
|
-
if (
|
|
36101
|
+
const reproduced = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
|
|
36102
|
+
if (reproduced.length === 0) return { blocking: 0, warning: 0 };
|
|
36103
|
+
const blocking = reproduced.filter((f) => formalSeverity(f) === "high");
|
|
36122
36104
|
const existing = Array.isArray(response.findings) ? response.findings : [];
|
|
36123
36105
|
response.findings = [
|
|
36124
36106
|
...existing,
|
|
36125
|
-
...
|
|
36107
|
+
...reproduced.map((f) => ({
|
|
36126
36108
|
pattern_id: "formal-counterexample",
|
|
36127
|
-
severity:
|
|
36109
|
+
severity: formalSeverity(f),
|
|
36128
36110
|
scope: "change",
|
|
36129
36111
|
file: f.file,
|
|
36130
36112
|
line: outcome.target?.lines?.from ?? null,
|
|
@@ -36132,14 +36114,15 @@ function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
|
36132
36114
|
// as a title it read backwards (e2e, 2026-09-26). A trace that needs a
|
|
36133
36115
|
// crash between two writes is labelled: reproducing it means injecting
|
|
36134
36116
|
// the crash, so it is a crash-consistency finding, not a race.
|
|
36135
|
-
title: `Formal: ${terminalText(f.symbol, 128)} breaks "${terminalText(f.desc, 280)}"${isCrashTrace(f.trace) ? " after a crash (crash consistency)" : ""} \u2014 reproduced on the real code`,
|
|
36117
|
+
title: `Formal: ${terminalText(f.symbol, 128)} breaks "${terminalText(f.desc, 280)}"${isCrashTrace(f.trace) ? " after a crash (crash consistency)" : ""} \u2014 reproduced on the real code${f.impact && f.impact !== "none" ? ` (impact: ${f.impact})` : ""}`,
|
|
36136
36118
|
description: terminalText(f.reason ?? "", 2e3),
|
|
36137
36119
|
fix: { description: `Trace: ${terminalText(f.trace.join(" \u2192 ") || "(initial state)", 2e3)}. Replay: ${terminalText(f.reason ?? "reproduced in the container", 2e3)}` },
|
|
36138
36120
|
source: "formal"
|
|
36139
36121
|
}))
|
|
36140
36122
|
];
|
|
36141
|
-
if (
|
|
36142
|
-
|
|
36123
|
+
if (blocking.length > 0) response.gate_decision = "FAIL";
|
|
36124
|
+
else if (response.gate_decision === "PASS" || response.gate_decision == null) response.gate_decision = "WARN";
|
|
36125
|
+
return { blocking: blocking.length, warning: reproduced.length - blocking.length };
|
|
36143
36126
|
}
|
|
36144
36127
|
|
|
36145
36128
|
// src/lib/prove/scalpel.ts
|
|
@@ -36458,7 +36441,9 @@ async function runFormalPhase(input) {
|
|
|
36458
36441
|
symbol: f.symbol,
|
|
36459
36442
|
verdict: "reproduced",
|
|
36460
36443
|
reason: f.waived ? `waived ${f.waived.at.slice(0, 10)}: ${f.waived.reason} (standing since ${f.at.slice(0, 10)}; the file has not changed)` : `${f.reason ?? "reproduced on the real code"} (standing since ${f.at.slice(0, 10)}; the file has not changed)`,
|
|
36461
|
-
...f.waived ? { waived: true } : {}
|
|
36444
|
+
...f.waived ? { waived: true } : {},
|
|
36445
|
+
// A hand-edited file cannot invent a code: unknown values read as absent (HIGH).
|
|
36446
|
+
...isImpact(f.impact) ? { impact: f.impact } : {}
|
|
36462
36447
|
});
|
|
36463
36448
|
}
|
|
36464
36449
|
}
|
|
@@ -36497,7 +36482,6 @@ async function runFormalPhase(input) {
|
|
|
36497
36482
|
return done({ status: "skipped", skip: "not-eligible", detail: `${best.file}: ${isEligible(best, largePush).why}` });
|
|
36498
36483
|
}
|
|
36499
36484
|
const ranked = await deps.rank(eligible, root, input.changedLines);
|
|
36500
|
-
if (!ranked.ok && ranked.disabled) return done({ status: "skipped", skip: "disabled-by-server", detail: ranked.error });
|
|
36501
36485
|
if (ranked.ok) spend(ranked);
|
|
36502
36486
|
candidates2 = ranked.ranked.slice(0, 5).map((c) => ({ file: c.file, symbol: c.ai?.symbol ?? null, score: c.ai?.score ?? null, criteria: c.ai?.criteria ?? null, reasked: !!c.ai?.reasked, reasons: (c.ai?.reasons ?? []).map((r) => r.slice(0, 200)) }));
|
|
36503
36487
|
if (cancelled()) return done({ status: "skipped", skip: "deadline", detail: "cut off after ranking" });
|
|
@@ -36732,7 +36716,7 @@ async function runFormalPhase(input) {
|
|
|
36732
36716
|
continue;
|
|
36733
36717
|
}
|
|
36734
36718
|
const verdict = r.verdict === "reproduced" ? "reproduced" : r.verdict === "diverged" ? "diverged" : r.verdict === "unreplayable" ? "model-only" : "unverified";
|
|
36735
|
-
findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0 });
|
|
36719
|
+
findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0, ...verdict === "reproduced" && r.impact ? { impact: r.impact } : {} });
|
|
36736
36720
|
}
|
|
36737
36721
|
const reproduced = findings.filter((f) => f.verdict === "reproduced");
|
|
36738
36722
|
if (reproduced.length > 0 && !cancelled()) {
|
|
@@ -36740,7 +36724,7 @@ async function runFormalPhase(input) {
|
|
|
36740
36724
|
const others = readPersistedFindings(root).filter((f) => f.file !== top.file);
|
|
36741
36725
|
writePersistedFindings(root, [
|
|
36742
36726
|
...others,
|
|
36743
|
-
...reproduced.map((f) => ({ file: f.file, symbol: f.symbol, sha256: sha2562, property: f.property, desc: f.desc, kind: f.kind, trace: f.trace, model: modelName, reason: f.reason, ...lines ? { lines } : {}, at: now }))
|
|
36727
|
+
...reproduced.map((f) => ({ file: f.file, symbol: f.symbol, sha256: sha2562, property: f.property, desc: f.desc, kind: f.kind, trace: f.trace, model: modelName, reason: f.reason, ...f.impact ? { impact: f.impact } : {}, ...lines ? { lines } : {}, at: now }))
|
|
36744
36728
|
]);
|
|
36745
36729
|
}
|
|
36746
36730
|
const clean2 = findings.every((f) => f.verdict === "diverged");
|
|
@@ -36861,8 +36845,6 @@ async function prepareFormalPhase(root, env = process.env, auth2, readSource, co
|
|
|
36861
36845
|
const apiKey = direct ? env.OPENAI_API_KEY : void 0;
|
|
36862
36846
|
const toolchain = findToolchain(env);
|
|
36863
36847
|
if (!toolchain) return { ok: false, skip: "no-toolchain" };
|
|
36864
|
-
const untrusted = proofsWorkspaceProblem(root);
|
|
36865
|
-
if (untrusted) return { ok: false, skip: "workspace-untrusted", detail: untrusted.slice(0, 200) };
|
|
36866
36848
|
try {
|
|
36867
36849
|
scaffoldProofs(root);
|
|
36868
36850
|
} catch (err2) {
|
|
@@ -37480,6 +37462,8 @@ async function runGuard(opts, globals) {
|
|
|
37480
37462
|
rounds: formalOutcome.model?.rounds ?? null,
|
|
37481
37463
|
properties: formalOutcome.properties?.map((p) => `${p.id}:${p.result}`) ?? [],
|
|
37482
37464
|
findings: formalOutcome.findings.map((f) => `${f.property}:${f.verdict}`),
|
|
37465
|
+
// Decision 82: the replay's impact code per reproduced finding; count/cache/internal warn, the rest block.
|
|
37466
|
+
impacts: formalOutcome.findings.filter((f) => f.impact).map((f) => `${f.property}:${f.impact}`),
|
|
37483
37467
|
// Decision 81: what each property rests on (a rule, or "line"), what was
|
|
37484
37468
|
// sent back or dropped as ungrounded, and which counterexamples lack their
|
|
37485
37469
|
// property's trigger (shadow). Ids and rule names only, never code.
|
|
@@ -38789,22 +38773,6 @@ var import_promises18 = require("node:stream/promises");
|
|
|
38789
38773
|
var import_node_stream = require("node:stream");
|
|
38790
38774
|
var RELEASES = "https://releases.lean-lang.org/lean4";
|
|
38791
38775
|
var LEAN_DOWNLOAD_SIZE = "\u2248800 MB download, 2.7 GB on disk";
|
|
38792
|
-
var DOWNLOAD_STALL_MS = 6e4;
|
|
38793
|
-
var LEAN_DISK_NEEDED_BYTES = 3.75 * 1024 ** 3;
|
|
38794
|
-
function freeBytes(dir) {
|
|
38795
|
-
let d = dir;
|
|
38796
|
-
for (let i2 = 0; i2 < 64; i2++) {
|
|
38797
|
-
try {
|
|
38798
|
-
const st = (0, import_node_fs63.statfsSync)(d);
|
|
38799
|
-
return Number(st.bavail) * Number(st.bsize);
|
|
38800
|
-
} catch {
|
|
38801
|
-
const up = (0, import_node_path47.join)(d, "..");
|
|
38802
|
-
if (up === d) return null;
|
|
38803
|
-
d = up;
|
|
38804
|
-
}
|
|
38805
|
-
}
|
|
38806
|
-
return null;
|
|
38807
|
-
}
|
|
38808
38776
|
var LEAN_ASSET_SHA256 = {
|
|
38809
38777
|
"v4.34.0": {
|
|
38810
38778
|
"lean-4.34.0-darwin.zip": "75a2a4b8e3d35680e8f51fae5a42c79702f463dd73ee1520fb61e5a9143e336c",
|
|
@@ -38863,32 +38831,17 @@ function unzip(zip, dest) {
|
|
|
38863
38831
|
}
|
|
38864
38832
|
return { ok: false, error: errors.join("; ") };
|
|
38865
38833
|
}
|
|
38866
|
-
async function download(url, dest, onProgress
|
|
38867
|
-
const
|
|
38868
|
-
|
|
38869
|
-
const
|
|
38870
|
-
|
|
38871
|
-
|
|
38872
|
-
|
|
38873
|
-
|
|
38874
|
-
|
|
38875
|
-
|
|
38876
|
-
|
|
38877
|
-
const total = Number(res.headers.get("content-length")) || null;
|
|
38878
|
-
let seen = 0;
|
|
38879
|
-
const stream = import_node_stream.Readable.fromWeb(res.body);
|
|
38880
|
-
stream.on("data", (chunk) => {
|
|
38881
|
-
arm();
|
|
38882
|
-
seen += chunk.length;
|
|
38883
|
-
onProgress(seen, total);
|
|
38884
|
-
});
|
|
38885
|
-
await (0, import_promises18.pipeline)(stream, (0, import_node_fs63.createWriteStream)(dest), { signal: ctrl.signal });
|
|
38886
|
-
} catch (err2) {
|
|
38887
|
-
if (ctrl.signal.aborted) throw new Error(`download stalled: nothing received for ${Math.round(stallMs / 1e3)} s`);
|
|
38888
|
-
throw err2;
|
|
38889
|
-
} finally {
|
|
38890
|
-
clearTimeout(timer);
|
|
38891
|
-
}
|
|
38834
|
+
async function download(url, dest, onProgress) {
|
|
38835
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
38836
|
+
if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status}`);
|
|
38837
|
+
const total = Number(res.headers.get("content-length")) || null;
|
|
38838
|
+
let seen = 0;
|
|
38839
|
+
const stream = import_node_stream.Readable.fromWeb(res.body);
|
|
38840
|
+
stream.on("data", (chunk) => {
|
|
38841
|
+
seen += chunk.length;
|
|
38842
|
+
onProgress(seen, total);
|
|
38843
|
+
});
|
|
38844
|
+
await (0, import_promises18.pipeline)(stream, (0, import_node_fs63.createWriteStream)(dest));
|
|
38892
38845
|
}
|
|
38893
38846
|
function inPlaceProgress(label2, write = (s) => process.stderr.write(s)) {
|
|
38894
38847
|
let active = false;
|
|
@@ -38926,12 +38879,6 @@ async function installLean(opts = {}) {
|
|
|
38926
38879
|
const dir = pinnedVersionDir(env);
|
|
38927
38880
|
const url = `${RELEASES}/${pinnedVersion()}/${asset}`;
|
|
38928
38881
|
const home = verityLeanHome(env);
|
|
38929
|
-
const free = freeBytes(home);
|
|
38930
|
-
const needed = opts.minFreeBytes ?? LEAN_DISK_NEEDED_BYTES;
|
|
38931
|
-
if (free !== null && free < needed) {
|
|
38932
|
-
const gb = (n) => (n / 1024 ** 3).toFixed(1);
|
|
38933
|
-
return { ok: false, error: `not enough disk space for Lean: ${gb(free)} GB free where ${home} lives, needs about ${gb(needed)} GB` };
|
|
38934
|
-
}
|
|
38935
38882
|
const tmp = (0, import_node_path47.join)(home, ".tmp");
|
|
38936
38883
|
(0, import_node_fs63.rmSync)(tmp, { recursive: true, force: true });
|
|
38937
38884
|
(0, import_node_fs63.mkdirSync)(tmp, { recursive: true });
|
|
@@ -39240,10 +39187,8 @@ var INTENSITY_CHOICES = [
|
|
|
39240
39187
|
{ id: "thorough", label: "thorough", hint: "all tools, all rules (~15s)" }
|
|
39241
39188
|
];
|
|
39242
39189
|
var FORMAL_CHOICES = [
|
|
39243
|
-
{ id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample" },
|
|
39244
|
-
|
|
39245
|
-
// 2.7 GB, and a bare Enter must never do that.
|
|
39246
|
-
{ id: "no", label: "no", hint: "enable later with: verity config formal push", recommended: true }
|
|
39190
|
+
{ id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample", recommended: true },
|
|
39191
|
+
{ id: "no", label: "no", hint: "enable later with: verity config formal push" }
|
|
39247
39192
|
];
|
|
39248
39193
|
var MOMENT_CHOICES = [
|
|
39249
39194
|
{ id: "stop", label: "On stop", hint: "after every agent turn \u2014 fast feedback while you work", recommended: true },
|
|
@@ -39649,20 +39594,16 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
|
|
|
39649
39594
|
printWarn(` Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`);
|
|
39650
39595
|
return;
|
|
39651
39596
|
}
|
|
39597
|
+
const toolchain = findToolchain();
|
|
39652
39598
|
if (cfg.formal === "push") {
|
|
39653
|
-
if (
|
|
39599
|
+
if (toolchain) printInfo(" Formal verification at push \u2713");
|
|
39654
39600
|
else printWarn(" Formal verification is on but Lean is missing \u2014 run: verity lean install");
|
|
39655
39601
|
return;
|
|
39656
39602
|
}
|
|
39657
|
-
if (cfg.formal === "off") {
|
|
39658
|
-
printInfo(" Formal verification: off (enable with: verity config formal push)");
|
|
39659
|
-
return;
|
|
39660
|
-
}
|
|
39661
39603
|
if (defaultsOnly || !interactive()) {
|
|
39662
39604
|
printInfo(" Formal verification (a model-based bug finder at push): off \u2014 enable with: verity config formal push");
|
|
39663
39605
|
return;
|
|
39664
39606
|
}
|
|
39665
|
-
const toolchain = findToolchain();
|
|
39666
39607
|
console.log("");
|
|
39667
39608
|
console.log(" Formal verification review \u2014 a model-based bug finder for your riskiest code.");
|
|
39668
39609
|
console.log(" At push, Verity picks the riskiest changed function, checks a small model of");
|
|
@@ -39670,10 +39611,9 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
|
|
|
39670
39611
|
console.log(" Verity for that). Only a reproduced bug can block; most pushes get no model.");
|
|
39671
39612
|
console.log(` Adds /verity-prove <worry> to check any code on demand.${toolchain ? "" : ` Needs to install Lean 4 (${LEAN_DOWNLOAD_SIZE}) on "yes".`}`);
|
|
39672
39613
|
console.log("");
|
|
39673
|
-
const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "
|
|
39614
|
+
const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "yes");
|
|
39674
39615
|
if (wants === "yes" && pluginMode) printInfo(" Plugin: the push check works now; /verity:verity-prove arrives with the plugin's next release.");
|
|
39675
39616
|
if (wants !== "yes") {
|
|
39676
|
-
writeProjectConfig({ formal: "off" });
|
|
39677
39617
|
printInfo(" Formal verification: off (enable later with: verity config formal push)");
|
|
39678
39618
|
return;
|
|
39679
39619
|
}
|
|
@@ -39932,7 +39872,7 @@ function registerInitCommand(program2) {
|
|
|
39932
39872
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
39933
39873
|
init: {
|
|
39934
39874
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39935
|
-
cli_version: true ? "0.34.0-experimental.
|
|
39875
|
+
cli_version: true ? "0.34.0-experimental.421a631" : "dev"
|
|
39936
39876
|
}
|
|
39937
39877
|
});
|
|
39938
39878
|
} catch (err2) {
|
|
@@ -41405,8 +41345,8 @@ function registerLeanCommand(program2) {
|
|
|
41405
41345
|
}
|
|
41406
41346
|
|
|
41407
41347
|
// src/cli.ts
|
|
41408
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.
|
|
41409
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.
|
|
41348
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.421a631").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
41349
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.421a631");
|
|
41410
41350
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
41411
41351
|
try {
|
|
41412
41352
|
await foldLegacyLocalCredential();
|
package/package.json
CHANGED