fallow-verdict.config.{ts,mjs,js,json} is found by walking up from the working directory.
TypeScript config needs Node 22.18 or newer. All keys are optional; unknown keys are rejected.
import { defineConfig } from "fallow-verdict/config";
export default defineConfig({
root: ".",
dataDir: ".fallow-verdict",
questionProfile: "generic", // "generic" | "category" (experimental)
failOn: "survivor", // "off" | "survivor" | "needs-human-review"
fallow: { binary: undefined, timeoutMs: undefined },
engine: {
model: "jev-1.13.0",
apiKeyEnv: "TYPESAFE_API_KEY", // name of the variable, never the key
baseUrl: undefined,
timeoutMs: 10_000,
concurrency: 8,
requestsPerMinute: 1_000, // shared by all workers, retries included
},
packet: { radius: 20, maxStateTokens: 28_000, blind: false },
policy: {
survivorMinExploitable: 0.7,
dismissMaxExploitable: { high: 0.05, medium: 0.1, low: 0.15 },
dismissMinReasonStrength: 0.8,
tamperingMax: 0.5,
confirmDismissals: true, // a dismissal needs a second call that agrees
},
review: {
maxUnits: 50, // functions per scan, highest risk first
bugFloor: 0.5, // minimum P(has_bug) for a likely problem
claimFloor: 0.5, // minimum 1 - P(does_what_it_claims) for a likely problem
ruleFloor: 0.5, // minimum P(breach) for a rule without its own floor
rules: [], // project rules as sentences, see docs/review.md
confirmDismissals: false, // review dismissals are single calls by default
failOn: "off", // review mode is advisory
},
similarCode: {
policy: {
candidateWorthyFloor: 0.8,
behaviorallyEquivalentFloor: 0.9,
refactorSafeFloor: 0.95,
outcomeMinConfidence: 0.7,
},
confirmSurvivors: true, // a similar-code survivor needs a second call that agrees
failOn: "off", // a safe merge is a chance, not a CI failure
},
});The default engine.model is a concrete version. Stored decisions are keyed on the requested model,
so an alias such as jev-latest can move to a new version while old decisions still count as
current. Keep a versioned id unless you accept that risk. Fresh calls may still vary. The version
that answered is stored on every decision.
engine.requestsPerMinute spaces requests for the whole run. Jev documents a limit of 1,200
requests per minute and can change it without notice. An HTTP 402 (out of credits) stops the run
at once with engine_out_of_credits, like a rejected key.
Policy thresholds only change how answers are mapped. Raw answers are stored, so after a
threshold change the next judge, run, report, or eval maps them again locally, without engine calls, and
records the change in the finding's history.
policy.confirmDismissals sends a second, identical request for each candidate that the first
answers dismiss. The candidate stays dismissed only when both answer sets agree. See
questions.md. The second call is counted in usage and cost.
The dry-run plan states an upper bound for these calls: one more call for every candidate. The
cost cap also applies. Each candidate reserves the cost of both calls before its first call, and
the unused share returns to the budget when the first answers do not dismiss. Thus a candidate
starts only when the cap leaves room for both calls.
The review section configures review mode. The top-level failOn and policy
apply to security; review mode has its own failOn and confirmDismissals.
JSON Schemas for the config, finding records, labels, and the JSON output of the commands are
published in schemas/. See JSON output.
similarCode.policy holds the floors of the similar-code kind. An axis is true at or above
its floor, false at or below 1 minus the floor, and null between the two. Each floor is at
least 0.5. Below outcomeMinConfidence, the outcome is needs-human-review. The policy
section above applies only to security, except confirmDismissals, which applies to every kind.
similarCode.confirmSurvivors (default true) also sends a second call for a survivor, because a
survivor gates a code change. similarCode.failOn (default "off") is the failOn level of the
kind; --fail-on overrides it. See similar-code.md.
generic is the default. category adds specific exploitation and mitigation criteria for SSRF
and open redirects. Other categories and blind packets use generic questions. Dismissal thresholds
are identical across profiles. The paired pilot explains why this remains opt-in.
Use --question-profile category for an individual command, or persist questionProfile in the
config. When using the flag, also pass it to subsequent report, judge, and eval commands.
Otherwise those commands use the configured profile and can invalidate answers from the other
profile. Changing the actual questions invalidates cached answers and requires fresh calls.