@codacy/verity-cli 0.33.3-experimental.5a3f573 → 0.33.3-experimental.6b251cc
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/bin/verity.js +152 -19
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -27021,7 +27021,7 @@ function channelSilence(input) {
|
|
|
27021
27021
|
// src/lib/cli-version.ts
|
|
27022
27022
|
function cliVersion() {
|
|
27023
27023
|
try {
|
|
27024
|
-
return true ? "0.33.3-experimental.
|
|
27024
|
+
return true ? "0.33.3-experimental.6b251cc" : "dev";
|
|
27025
27025
|
} catch {
|
|
27026
27026
|
return "dev";
|
|
27027
27027
|
}
|
|
@@ -27856,11 +27856,11 @@ var rootCandidateCache = /* @__PURE__ */ new Map();
|
|
|
27856
27856
|
function candidateRoots(repoRoot2) {
|
|
27857
27857
|
const cached2 = rootCandidateCache.get(repoRoot2);
|
|
27858
27858
|
if (cached2) return cached2;
|
|
27859
|
-
const
|
|
27860
|
-
const out2 = [
|
|
27859
|
+
const norm2 = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
27860
|
+
const out2 = [norm2];
|
|
27861
27861
|
try {
|
|
27862
|
-
const real = import_node_fs42.realpathSync.native(
|
|
27863
|
-
if (real !==
|
|
27862
|
+
const real = import_node_fs42.realpathSync.native(norm2).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
27863
|
+
if (real !== norm2) out2.push(real);
|
|
27864
27864
|
} catch {
|
|
27865
27865
|
}
|
|
27866
27866
|
for (const r of [...out2]) {
|
|
@@ -33555,6 +33555,47 @@ function collectRiskSignals(root, opts = {}) {
|
|
|
33555
33555
|
return out2.slice(0, opts.limit ?? 10);
|
|
33556
33556
|
}
|
|
33557
33557
|
|
|
33558
|
+
// src/lib/prove/grounding.ts
|
|
33559
|
+
var BASIS_RULES = ["conservation", "once", "no-lost-update", "fail-closed", "progress"];
|
|
33560
|
+
var RULE = /^rule:([a-z-]+)$/;
|
|
33561
|
+
var LINE = /^(?:([^\s:]+):)?L?(\d{1,6})(?::\s*|\s+)(?:comment\s+)?([\s\S]+)$/;
|
|
33562
|
+
var norm = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
33563
|
+
function basisOf(source2, files) {
|
|
33564
|
+
const s = source2.trim();
|
|
33565
|
+
const r = RULE.exec(s);
|
|
33566
|
+
if (r) return BASIS_RULES.includes(r[1]) ? { ok: true, basis: s } : { ok: false, why: `"rule:${r[1]}" is not one of the rules` };
|
|
33567
|
+
const m = LINE.exec(s);
|
|
33568
|
+
if (!m) return { ok: false, why: s ? `"${s.slice(0, 60)}" is neither L<n>: <words> nor a rule` : "no source" };
|
|
33569
|
+
const path = m[1];
|
|
33570
|
+
const n = Number(m[2]);
|
|
33571
|
+
const quoted = m[3].trim().replace(/^["'`“”]+|["'`“”]+$/g, "");
|
|
33572
|
+
const fragments = quoted.split(/…|\.\.\./).map(norm).filter((f) => f.length >= 4);
|
|
33573
|
+
if (fragments.length === 0) return { ok: false, why: `L${n}: too few words to find on the line` };
|
|
33574
|
+
const pool = path ? [...files].filter(([f]) => f === path || f.endsWith(`/${path}`)).map(([, c]) => c) : [...files.values()];
|
|
33575
|
+
if (path && pool.length === 0) return { ok: false, why: `${path} is not one of the given files` };
|
|
33576
|
+
for (const content of pool) {
|
|
33577
|
+
const line2 = content.split("\n")[n - 1];
|
|
33578
|
+
if (line2 !== void 0 && fragments.every((f) => norm(line2).includes(f))) return { ok: true, basis: `L${n}: ${quoted.slice(0, 120)}` };
|
|
33579
|
+
}
|
|
33580
|
+
return { ok: false, why: `line ${n} does not contain "${quoted.slice(0, 60)}"` };
|
|
33581
|
+
}
|
|
33582
|
+
var NOT_A_MENTION = "(?<!\\b(?:even|including|also|or) )";
|
|
33583
|
+
var UNTIL_FIRST = "(?:(?!\\bor\\b)[^,.;:]){0,60}?";
|
|
33584
|
+
var NEEDS_FAILURE = new RegExp(`${NOT_A_MENTION}\\b(?:after|when|if|once|on)\\b${UNTIL_FIRST}\\b(?:fails?|failed|errors?|throws?|times? out|timed out|rejects?|rejected|is unavailable|is down)\\b|${NOT_A_MENTION}\\bon (?:an? )?(?:error|failure|timeout)\\b|\\ba failed\\b`);
|
|
33585
|
+
var NEEDS_CRASH = new RegExp(`${NOT_A_MENTION}\\b(?:after|when|if|on)\\b${UNTIL_FIRST}\\bcrash`);
|
|
33586
|
+
var HAS_FAILURE = /fail|error|throw|timeout|timedout|timesout|reject|exception|unavailable|down/i;
|
|
33587
|
+
var HAS_CRASH = /crash|die|kill|abort|restart/i;
|
|
33588
|
+
function missingTrigger(desc, trace) {
|
|
33589
|
+
const d = desc.toLowerCase();
|
|
33590
|
+
const t = trace.join(" ");
|
|
33591
|
+
if (NEEDS_CRASH.test(d) && !HAS_CRASH.test(t)) return "a crash";
|
|
33592
|
+
if (NEEDS_FAILURE.test(d) && !HAS_FAILURE.test(t)) return "a failure";
|
|
33593
|
+
return null;
|
|
33594
|
+
}
|
|
33595
|
+
function ungroundedMessage(list2) {
|
|
33596
|
+
return `These properties do not say what they rest on \u2014 ${list2}. Set each property's \`source\` to "L<n>: <words copied from that line>" (a line of the numbered source that states the promise) or one rule: ${BASIS_RULES.map((r) => `rule:${r}`).join(", ")}. Drop a property nothing supports; keep the model otherwise the same.`;
|
|
33597
|
+
}
|
|
33598
|
+
|
|
33558
33599
|
// src/lib/prove/ranker.ts
|
|
33559
33600
|
var STRUCTURAL_CATALOGUE = [
|
|
33560
33601
|
"idempotence",
|
|
@@ -34121,6 +34162,8 @@ var MAX_CONTEXT_EFFECTS = 20;
|
|
|
34121
34162
|
var MAX_CO_ACCESSORS = 2;
|
|
34122
34163
|
var MAX_HELPERS2 = 3;
|
|
34123
34164
|
var MAX_CONTEXT_LINES = 120;
|
|
34165
|
+
var MAX_CALLERS2 = 3;
|
|
34166
|
+
var MAX_CALLER_LINES = 9;
|
|
34124
34167
|
var MAX_HELPER_LINES = 80;
|
|
34125
34168
|
var MAX_CONTEXT_BYTES = 48 * 1024;
|
|
34126
34169
|
var LIBRARY_API = `The Verity Lean 4 library (already built; \`import Verity\` is the ONLY import allowed):
|
|
@@ -34180,7 +34223,7 @@ def model : Model :=
|
|
|
34180
34223
|
{ name := "SeedRunner"
|
|
34181
34224
|
abstracts := [ { file := "cli/src/lib/seed-runner.ts", symbol := "runSeed", sha256 := "<sha256>" } ] }
|
|
34182
34225
|
|
|
34183
|
-
def P1 : Property := { id := "P1", kind := .structural, desc := "seeding runs at most once: posts never exceed 1" }
|
|
34226
|
+
def P1 : Property := { id := "P1", kind := .structural, desc := "seeding runs at most once: posts never exceed 1", source := "rule:once" }
|
|
34184
34227
|
|
|
34185
34228
|
#eval Verity.model model
|
|
34186
34229
|
#eval Verity.check model P1 sys (fun s => s.posts \u2264 1) { bound := 4 }
|
|
@@ -34240,8 +34283,8 @@ def model : Model :=
|
|
|
34240
34283
|
{ name := "Withdraw"
|
|
34241
34284
|
abstracts := [ { file := "src/accounts/withdraw.ts", symbol := "withdraw", sha256 := "<sha256>", lines := "5-12" } ] }
|
|
34242
34285
|
|
|
34243
|
-
def P1 : Property := { id := "no_overdraft_under_concurrent_withdrawals", kind := .structural, desc := "two concurrent withdrawals never pay out more than the balance held: the balance check and the debit act as one step" }
|
|
34244
|
-
def P2 : Property := { id := "balance_matches_payouts", kind := .structural, desc := "once both calls are done, the balance plus everything paid out equals the starting balance (no lost update)" }
|
|
34286
|
+
def P1 : Property := { id := "no_overdraft_under_concurrent_withdrawals", kind := .structural, desc := "two concurrent withdrawals never pay out more than the balance held: the balance check and the debit act as one step", source := "rule:conservation" }
|
|
34287
|
+
def P2 : Property := { id := "balance_matches_payouts", kind := .structural, desc := "once both calls are done, the balance plus everything paid out equals the starting balance (no lost update)", source := "rule:no-lost-update" }
|
|
34245
34288
|
|
|
34246
34289
|
#eval Verity.model model
|
|
34247
34290
|
#eval Verity.check model P1 sys (fun s => s.paidOut \u2264 10) { bound := 6 }
|
|
@@ -34264,7 +34307,7 @@ def model : Model :=
|
|
|
34264
34307
|
{ name := "Split"
|
|
34265
34308
|
abstracts := [ { file := "src/payouts/split.ts", symbol := "splitPayout", sha256 := "<sha256>", lines := "4-8" } ] }
|
|
34266
34309
|
|
|
34267
|
-
def P1 : Property := { id := "parts_sum_to_total", kind := .structural, desc := "the parts of a payout always add up to the total paid out" }
|
|
34310
|
+
def P1 : Property := { id := "parts_sum_to_total", kind := .structural, desc := "the parts of a payout always add up to the total paid out", source := "rule:conservation" }
|
|
34268
34311
|
|
|
34269
34312
|
#eval Verity.model model
|
|
34270
34313
|
#eval Verity.checkData model P1 [(100, [1, 1, 1]), (2, [1, 1, 1]), (10, [1, 2]), (7, [3, 4])] (fun (x : Nat \xD7 List Nat) => (split x.1 x.2).foldl (fun a b => a + b) 0 == x.1)
|
|
@@ -34295,6 +34338,8 @@ STEP 3 \u2014 Properties: 2 to 4, in this order of priority:
|
|
|
34295
34338
|
A counterexample must run the real function: a trace made only of environment moves (another writer, a clock) says nothing about this code. A promise about the END result (nothing lost, nothing charged twice, totals add up) is checked on states where every invocation has finished or crashed \u2014 guard the \`inv\` with the program counters, as exemplar 2's second property does; a violation that exists only while work is still pending is not a bug.
|
|
34296
34339
|
A property is about an OUTCOME someone would call a bug \u2014 money, stock or records wrong, access granted, work lost or done twice, a job stuck forever \u2014 never about the mechanism's internals (a key's format, two timestamps being distinct, a lease's exact boundary tick). A comment that says HOW the code works ("make each attempt's key unique") is not the promise; the promise is what that mechanism is for.
|
|
34297
34340
|
A comment that states a SAFE design choice ("a spent token presented again ends every session", "consumers dedupe, so publishing twice is harmless") is the design: never claim the opposite. A comment never makes an UNSAFE outcome safe: granting access on an error, losing or double-counting money, stock or records is a bug whatever the comment says ("don't block people when the permission service is flaky" describes a fail-open bug).
|
|
34341
|
+
Every property says what it rests on, in \`source\`: either \`L<n>: <words copied from that line>\` \u2014 a line of the numbered source above (a comment, docstring, check, error message or name) that states the promise \u2014 or one rule: \`rule:conservation\` (money, stock or counts add up), \`rule:once\` (no double effect, only where the code dedups, as above), \`rule:no-lost-update\`, \`rule:fail-closed\` (an error never grants access or reports success), \`rule:progress\` (nothing stuck forever). A property that neither a line nor one of these rules supports is not a promise: do not state it. A property whose \`source\` is empty, or cites words its line does not contain, sends the model back.
|
|
34342
|
+
When the input lists where the function is called, a caller's guard bounds its inputs: never build a counterexample from an input no caller can pass (an empty list a caller refuses).
|
|
34298
34343
|
Property ids name the promise in snake_case (refund_total_within_capture) \u2014 never P1. \`desc\` states the promise in the code's own terms and names the move the trace needs ("two concurrent calls \u2026", "after the charge succeeded and the call timed out \u2026").
|
|
34299
34344
|
A PURE computation (no effect sites) is checked with \`Verity.checkData\` on small concrete inputs, including the edge cases (exemplar 3).
|
|
34300
34345
|
|
|
@@ -34434,6 +34479,10 @@ ${t.effects.slice(0, MAX_CONTEXT_EFFECTS).map((e) => ` ${e}`).join("\n")}`);
|
|
|
34434
34479
|
parts2.push("", "# Helpers it calls (their effects happen at the line that calls them)");
|
|
34435
34480
|
for (const h of t.helpers.slice(0, MAX_HELPERS2)) parts2.push("", `## ${h.file} \u2014 \`${h.symbol}\` (lines ${h.lines.from}\u2013${h.lines.to})`, "```", h.numbered, "```");
|
|
34436
34481
|
}
|
|
34482
|
+
if (t.callers?.length) {
|
|
34483
|
+
parts2.push("", "# Where it is called (a caller's guard bounds the inputs it can get)");
|
|
34484
|
+
for (const c of t.callers.slice(0, MAX_CALLERS2)) parts2.push("", `## ${c.file}:${c.line}${c.caller ? ` \u2014 in \`${c.caller}\`` : ""}`, "```", c.numbered, "```");
|
|
34485
|
+
}
|
|
34437
34486
|
if (previous) {
|
|
34438
34487
|
parts2.push("", "# Your previous attempt was not accepted \u2014 the diagnostics say why. Fix it (same model, same properties):", "```lean", previous.source.slice(0, 32 * 1024), "```", "Diagnostics:", "```", previous.error.slice(0, 4e3), "```");
|
|
34439
34488
|
}
|
|
@@ -34667,7 +34716,7 @@ Rules:
|
|
|
34667
34716
|
- A pure computation: call the real function on the counterexample's input.
|
|
34668
34717
|
- A deadlock or a hang: run under a timeout (asyncio.wait_for, Promise.race with a timer) and treat the timeout as the observation.
|
|
34669
34718
|
The harness must CALL the real function: a trace replayed on the stubs alone is not a reproduction \u2014 answer unreplayable.
|
|
34670
|
-
4. COMPUTE the verdict in the harness: print the observable state after each action; let every timer, promise and retry the trace did not crash or cancel run to completion; then evaluate the property
|
|
34719
|
+
4. COMPUTE the verdict in the harness: print the observable state after each action; let every timer, promise and retry the trace did not crash or cancel run to completion; then evaluate the property on the final real state AS ITS DESCRIPTION STATES \u2014 the harm it names, including what must happen first ("after X fails", "after a crash", "two concurrent calls"): a run that never does that thing has not violated it. Drive the code only with inputs its own callers in the source can pass. Print exactly one line, REPLAY_RESULT=reproduced when the property is violated or REPLAY_RESULT=diverged when it holds \u2014 chosen by an if/else or a conditional expression in the harness, never a fixed string.
|
|
34671
34720
|
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.
|
|
34672
34721
|
6. Nothing you run can reach the network. Do not try.
|
|
34673
34722
|
7. Keep it under about eight shell calls. Output tokens are time.
|
|
@@ -35974,9 +36023,9 @@ function isCrashTrace(trace) {
|
|
|
35974
36023
|
}
|
|
35975
36024
|
function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
35976
36025
|
if (!outcome) return { blocking: 0 };
|
|
35977
|
-
const
|
|
35978
|
-
const pushed = new Set(pushedFiles.map(
|
|
35979
|
-
const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(
|
|
36026
|
+
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
36027
|
+
const pushed = new Set(pushedFiles.map(norm2));
|
|
36028
|
+
const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
|
|
35980
36029
|
if (blocking.length === 0) return { blocking: 0 };
|
|
35981
36030
|
const existing = Array.isArray(response.findings) ? response.findings : [];
|
|
35982
36031
|
response.findings = [
|
|
@@ -36058,6 +36107,7 @@ function recordVerified(root, rec, now = Date.now()) {
|
|
|
36058
36107
|
var import_node_crypto17 = require("node:crypto");
|
|
36059
36108
|
var CONTEXT_BUDGET_MS = 2e3;
|
|
36060
36109
|
var sha256 = (s) => (0, import_node_crypto17.createHash)("sha256").update(s).digest("hex");
|
|
36110
|
+
var TEST_PATH4 = /(^|\/)(tests?|__tests__|spec)\/|\.(test|spec)\.[a-z]+$/;
|
|
36061
36111
|
function slice(content, f, maxLines) {
|
|
36062
36112
|
const all = content.split("\n");
|
|
36063
36113
|
const from = f.start.line;
|
|
@@ -36095,12 +36145,37 @@ function targetContext(opts) {
|
|
|
36095
36145
|
budget -= s.numbered.length;
|
|
36096
36146
|
helpers.push({ file: opts.file, symbol: h.qualified, lines: s.lines, sha256: sha256(opts.content), numbered: s.numbered, touches: [] });
|
|
36097
36147
|
}
|
|
36148
|
+
const callers = [];
|
|
36149
|
+
if (opts.codeSource) {
|
|
36150
|
+
const hits = opts.codeMap.callers(fn.value.name, { definedIn: opts.file, source: opts.codeSource, deadlineAt: Date.now() + 800, limit: 8 });
|
|
36151
|
+
const found = hits.source === "tree-sitter" ? hits.value.callers : [];
|
|
36152
|
+
const ranked = [...found.filter((c) => c.provenance !== "name"), ...found.filter((c) => c.provenance === "name")];
|
|
36153
|
+
for (const c of ranked) {
|
|
36154
|
+
if (callers.length >= MAX_CALLERS2) break;
|
|
36155
|
+
if (TEST_PATH4.test(c.path) || c.path === opts.file && c.line >= fn.value.start.line && c.line <= fn.value.end.line) continue;
|
|
36156
|
+
let content = contents.get(c.path);
|
|
36157
|
+
if (content === void 0) {
|
|
36158
|
+
const read2 = opts.codeSource.read(c.path, 512 * 1024);
|
|
36159
|
+
if (!read2.ok) continue;
|
|
36160
|
+
content = read2.content;
|
|
36161
|
+
contents.set(c.path, content);
|
|
36162
|
+
}
|
|
36163
|
+
const all = content.split("\n");
|
|
36164
|
+
const half = Math.floor(MAX_CALLER_LINES / 2);
|
|
36165
|
+
const from = Math.max(1, c.line - half);
|
|
36166
|
+
const numbered2 = numberLines(all.slice(from - 1, Math.min(all.length, from + MAX_CALLER_LINES - 1)).join("\n"), from);
|
|
36167
|
+
if (numbered2.length > budget) continue;
|
|
36168
|
+
budget -= numbered2.length;
|
|
36169
|
+
callers.push({ file: c.path, line: c.line, caller: c.caller, numbered: numbered2 });
|
|
36170
|
+
}
|
|
36171
|
+
}
|
|
36098
36172
|
const effects = a.effects.slice(0, MAX_CONTEXT_EFFECTS).map(renderEffect);
|
|
36099
36173
|
return {
|
|
36100
36174
|
fields: {
|
|
36101
36175
|
...effects.length ? { effects } : {},
|
|
36102
36176
|
...coAccessors.length ? { coAccessors } : {},
|
|
36103
|
-
...helpers.length ? { helpers } : {}
|
|
36177
|
+
...helpers.length ? { helpers } : {},
|
|
36178
|
+
...callers.length ? { callers } : {}
|
|
36104
36179
|
},
|
|
36105
36180
|
files: [...new Set(coAccessors.map((c) => c.file))],
|
|
36106
36181
|
signals: a.signals,
|
|
@@ -36233,9 +36308,11 @@ async function runFormalPhase(input) {
|
|
|
36233
36308
|
progress.usage = usage;
|
|
36234
36309
|
progress.step = "select";
|
|
36235
36310
|
let candidates2;
|
|
36311
|
+
const grounding = { sentBack: 0, dropped: [] };
|
|
36236
36312
|
const done = (partial) => ({
|
|
36237
36313
|
findings: [],
|
|
36238
36314
|
...candidates2 ? { candidates: candidates2 } : {},
|
|
36315
|
+
...grounding.sentBack || grounding.dropped.length ? { grounding } : {},
|
|
36239
36316
|
...partial,
|
|
36240
36317
|
usage,
|
|
36241
36318
|
durationMs: deps.now() - started
|
|
@@ -36426,6 +36503,7 @@ async function runFormalPhase(input) {
|
|
|
36426
36503
|
}
|
|
36427
36504
|
const top = chosen.top;
|
|
36428
36505
|
const sha2562 = sha256Of(chosen.content);
|
|
36506
|
+
const chosenContent = chosen.content;
|
|
36429
36507
|
const { lines, numbered: numbered2 } = chosen;
|
|
36430
36508
|
const target = { file: top.file, symbol: top.ai.symbol, lines, score: top.ai.score, reasons: [...top.reasons, ...top.ai.reasons], extent: chosen.extent, ...chosen.context ? { context: chosen.context.stats } : {}, ...floor ? { floor: true } : {} };
|
|
36431
36509
|
progress.target = target;
|
|
@@ -36467,6 +36545,27 @@ async function runFormalPhase(input) {
|
|
|
36467
36545
|
if (abstracts.length === 0 || stray || abstracts[0].file !== top.file) {
|
|
36468
36546
|
return done({ status: "skipped", skip: "model-header-mismatch", detail: `model abstracts ${stray?.file ?? "nothing"}, target was ${top.file}`, target, model: { name: gen.name, file: rel, rounds: round } });
|
|
36469
36547
|
}
|
|
36548
|
+
const files2 = /* @__PURE__ */ new Map([[top.file, chosenContent]]);
|
|
36549
|
+
for (const f of headerFiles) if (!files2.has(f)) {
|
|
36550
|
+
const r = readSrc(f, 512 * 1024);
|
|
36551
|
+
if (r.ok) files2.set(f, r.content);
|
|
36552
|
+
}
|
|
36553
|
+
const bad = report.properties.flatMap((p) => {
|
|
36554
|
+
const g = basisOf(p.source, files2);
|
|
36555
|
+
return g.ok ? [] : [`${p.id}: ${g.why}`];
|
|
36556
|
+
});
|
|
36557
|
+
if (bad.length > 0) {
|
|
36558
|
+
if (round < 2 && timeLeft() >= GENERATE_RESERVE_MS / 2) {
|
|
36559
|
+
grounding.sentBack++;
|
|
36560
|
+
previous = { source: gen.source, error: ungroundedMessage(bad.join("; ")) };
|
|
36561
|
+
report = null;
|
|
36562
|
+
continue;
|
|
36563
|
+
}
|
|
36564
|
+
const grounded = report.properties.filter((p) => basisOf(p.source, files2).ok);
|
|
36565
|
+
grounding.dropped = report.properties.filter((p) => !grounded.includes(p)).map((p) => p.id);
|
|
36566
|
+
if (grounded.length === 0) return done({ status: "skipped", skip: "model-did-not-elaborate", detail: `no property says what it rests on: ${bad.join("; ").slice(0, 240)}`, target, model: { name: gen.name, file: rel, rounds: round } });
|
|
36567
|
+
report = { ...report, properties: grounded };
|
|
36568
|
+
}
|
|
36470
36569
|
break;
|
|
36471
36570
|
}
|
|
36472
36571
|
previous = { source: gen.source, error: report.error ?? (report.malformed ? "the model printed malformed VERITY lines" : "the model reported no property") };
|
|
@@ -36485,7 +36584,11 @@ async function runFormalPhase(input) {
|
|
|
36485
36584
|
} catch {
|
|
36486
36585
|
}
|
|
36487
36586
|
const isCrash = (p) => isCrashTrace(p.trace ?? []) || /\bcrash/i.test(p.desc);
|
|
36488
|
-
const
|
|
36587
|
+
const skipsTrigger = new Map(report.properties.filter((p) => p.result === "counterexample").flatMap((p) => {
|
|
36588
|
+
const missing = missingTrigger(p.desc, p.trace ?? []);
|
|
36589
|
+
return missing ? [[p.id, missing]] : [];
|
|
36590
|
+
}));
|
|
36591
|
+
const cexes = report.properties.filter((p) => p.result === "counterexample" && !skipsTrigger.has(p.id)).map((p, i2) => ({ p, i: i2 })).sort((a, b) => Number(isCrash(a.p)) - Number(isCrash(b.p)) || a.i - b.i).map(({ p }) => p);
|
|
36489
36592
|
const baseOf = (p) => ({ property: p.id, desc: p.desc, kind: p.kind, trace: p.trace ?? [], file: top.file, symbol: top.ai.symbol });
|
|
36490
36593
|
const maxReplays = input.maxReplays ?? MAX_REPLAYS;
|
|
36491
36594
|
let skipWhy = null;
|
|
@@ -36520,6 +36623,10 @@ async function runFormalPhase(input) {
|
|
|
36520
36623
|
}).finally(settle);
|
|
36521
36624
|
}
|
|
36522
36625
|
});
|
|
36626
|
+
for (const p of report.properties) {
|
|
36627
|
+
const missing = skipsTrigger.get(p.id);
|
|
36628
|
+
if (missing) findings.push({ ...baseOf(p), verdict: "model-only", reason: `not replayed: the property needs ${missing} first, and the counterexample has none` });
|
|
36629
|
+
}
|
|
36523
36630
|
const replayedIds = new Set(toReplay.map((p) => p.id));
|
|
36524
36631
|
for (const p of cexes) {
|
|
36525
36632
|
const r = byId.get(p.id);
|
|
@@ -36625,7 +36732,24 @@ async function generateAndCheck(root, req, opts) {
|
|
|
36625
36732
|
const abstracts = report.header?.abstracts ?? [];
|
|
36626
36733
|
const stray = abstracts.find((a) => !headerFiles.has(a.file)) ?? (abstracts[0] && abstracts[0].file !== req.file ? abstracts[0] : void 0);
|
|
36627
36734
|
if (report.ok && report.properties.length > 0 && abstracts.length > 0 && !stray) {
|
|
36628
|
-
|
|
36735
|
+
const files = /* @__PURE__ */ new Map([[req.file, read2.content]]);
|
|
36736
|
+
for (const f of headerFiles) if (!files.has(f)) {
|
|
36737
|
+
const r = readSrc(f, 512 * 1024);
|
|
36738
|
+
if (r.ok) files.set(f, r.content);
|
|
36739
|
+
}
|
|
36740
|
+
const bad = report.properties.flatMap((p) => {
|
|
36741
|
+
const g = basisOf(p.source, files);
|
|
36742
|
+
return g.ok ? [] : [`${p.id}: ${g.why}`];
|
|
36743
|
+
});
|
|
36744
|
+
if (bad.length === 0) return { ok: true, name: gen.name, file: rel, report, rounds: round, costUsd };
|
|
36745
|
+
lastError = ungroundedMessage(bad.join("; "));
|
|
36746
|
+
if (round < maxRounds) {
|
|
36747
|
+
previous = { source: gen.source, error: lastError };
|
|
36748
|
+
continue;
|
|
36749
|
+
}
|
|
36750
|
+
const grounded = report.properties.filter((p) => basisOf(p.source, files).ok);
|
|
36751
|
+
if (grounded.length > 0) return { ok: true, name: gen.name, file: rel, report: { ...report, properties: grounded }, rounds: round, costUsd };
|
|
36752
|
+
break;
|
|
36629
36753
|
}
|
|
36630
36754
|
lastError = stray ? `the model abstracts ${stray.file}, not ${req.file}` : report.error ?? (report.malformed ? "the model printed malformed VERITY lines" : "the model reported no property");
|
|
36631
36755
|
previous = { source: gen.source, error: lastError };
|
|
@@ -37258,6 +37382,15 @@ async function runGuard(opts, globals) {
|
|
|
37258
37382
|
rounds: formalOutcome.model?.rounds ?? null,
|
|
37259
37383
|
properties: formalOutcome.properties?.map((p) => `${p.id}:${p.result}`) ?? [],
|
|
37260
37384
|
findings: formalOutcome.findings.map((f) => `${f.property}:${f.verdict}`),
|
|
37385
|
+
// Decision 81: what each property rests on (a rule, or "line"), what was
|
|
37386
|
+
// sent back or dropped as ungrounded, and what was not replayed for
|
|
37387
|
+
// lacking its property's trigger. Ids and rule names only, never code.
|
|
37388
|
+
grounding: {
|
|
37389
|
+
sources: (formalOutcome.properties ?? []).map((p) => /^rule:[a-z-]+$/.test(p.source) ? p.source : p.source ? "line" : "none"),
|
|
37390
|
+
sent_back: formalOutcome.grounding?.sentBack ?? 0,
|
|
37391
|
+
dropped: formalOutcome.grounding?.dropped ?? [],
|
|
37392
|
+
trigger_skipped: formalOutcome.findings.filter((f) => /^not replayed: the property needs/.test(f.reason ?? "")).map((f) => f.property)
|
|
37393
|
+
},
|
|
37261
37394
|
input_tokens: formalOutcome.usage.inputTokens,
|
|
37262
37395
|
output_tokens: formalOutcome.usage.outputTokens,
|
|
37263
37396
|
cost_usd: formalOutcome.usage.costUsd,
|
|
@@ -39650,7 +39783,7 @@ function registerInitCommand(program2) {
|
|
|
39650
39783
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
39651
39784
|
init: {
|
|
39652
39785
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39653
|
-
cli_version: true ? "0.33.3-experimental.
|
|
39786
|
+
cli_version: true ? "0.33.3-experimental.6b251cc" : "dev"
|
|
39654
39787
|
}
|
|
39655
39788
|
});
|
|
39656
39789
|
} catch (err2) {
|
|
@@ -41123,8 +41256,8 @@ function registerLeanCommand(program2) {
|
|
|
41123
41256
|
}
|
|
41124
41257
|
|
|
41125
41258
|
// src/cli.ts
|
|
41126
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.
|
|
41127
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.
|
|
41259
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.6b251cc").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) => {
|
|
41260
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.6b251cc");
|
|
41128
41261
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
41129
41262
|
try {
|
|
41130
41263
|
await foldLegacyLocalCredential();
|
package/package.json
CHANGED