The Three-Tier Analysis Model
Gadriel Code analyzes source through three layers, each with a distinct job, a distinct trust level, and a distinct default. The guiding principle:
Math finds. Graph understands. Claude explains.
Only the deterministic engine (Tier 0) decides what gates CI. The other two tiers add corroboration, external-tool aggregation, and semantic reach — but they are advisory, and neither can weaken a deterministic verdict.
Related: AI-coding integration · Privacy boundary
Overview
| Tier | What it is | Runs where | Default | Role in CI |
|---|---|---|---|---|
| Tier 0 | In-house deterministic engine (pattern / AST / taint) | Local, offline | Always on | Gate of record |
| Tier 1 | Your own locally-installed analyzers (clang-tidy, gosec, bandit, eslint-security, …) | Local subprocess | Off (--corroborate) | Advisory only |
| Tier 2 | The host AI assistant's already-running LLM, borrowed via MCP | Host agent's own LLM connection | Off (opt-in) | Advisory only — never gates |
┌───────────────────────────────────────┐
source on disk ─▶│ TIER 0 deterministic engine │──▶ findings.json
│ pattern · AST · taint │ (THE GATE)
└───────────────────────────────────────┘
│ ▲
(opt-in) --corroborate │ │ union / dedup (additive)
▼ │
┌───────────────────────────────────────┐
your scanners ──▶│ TIER 1 gosec / bandit / eslint / … │──▶ corroborated_by
└───────────────────────────────────────┘ (advisory)
│ ▲
(opt-in, default OFF) │ │ submit_ai_findings (advisory)
▼ │
┌───────────────────────────────────────┐
host LLM (MCP) ─▶│ TIER 2 authz · IDOR · business-logic │──▶ ai_advisory
└───────────────────────────────────────┘ (never gates)
Tier 0 — the deterministic engine (the gate of record)
Gadriel's own engine: tree-sitter-based parsing, AST analysis, and taint/flow
tracking, running entirely on the developer's machine or their CI runner. It
is always on, needs no network, and its output is the single source of
truth for --fail-on, compliance KPIs, "Open Criticals", and OCSF emission.
Everything downstream is measured against this floor. Tiers 1 and 2 exist to complement it (catch bugs it structurally cannot reach, or triage its findings), never to replace or override it.
Tier 1 — orchestrate the developer's own analyzers
Gadriel does not need to out-analyze a language's best OSS scanner to benefit from running it — it needs to aggregate. Tier 1 makes Gadriel the intelligence layer (cross-stream correlation, effective-risk re-scoring, domain gating, waivers, compliance-mapped reports) over scanners you already have installed.
bashgadriel code scan --corroborate
- Opt-in, off by default.
--corroborateis a single global opt-in; a test (corroborate_flag_is_opt_in_and_off_by_default) locks that in. It can also be set via[scan] corroboratein.gadriel.toml(defaultfalse). - Advisory only. In
doctor, Tier-1 checks areWarn, neverFail— a missing or too-old toolchain never hard-gates; CI gets one advisory line and proceeds on Tier 0. - Local subprocesses on source already on disk — the same shape as the C
clang-tidy driver, covered by the privacy boundary. (The one guardrail:
a semgrep adapter must pin
--configto a vendored local ruleset and never passauto/registry references, which would resolve rules over the network.)
Adapters:
| Adapter | Language | Invocation | Output |
|---|---|---|---|
| gosec | Go | gosec -fmt=sarif ./... | SARIF |
| bandit | Python | bandit -r <path> -f json | JSON |
| eslint-security | JS/TS | eslint --format json | ESLint JSON |
| SpotBugs + FindSecBugs | Java | spotbugs -sarif -pluginList findsecbugs.jar | SARIF |
| clippy / cargo-audit | Rust | cargo clippy / cargo audit --json | JSON (candidate; may not ship) |
Merge policy is additive, not suppressive. On overlap
(normalized_path, line ± window, cwe_family) Gadriel keeps the higher
confidence tier; ties keep the in-house finding (richer taint metadata) and
attach the external (adapter_id, rule_id) as corroborated_by. On no
overlap, both survive — most of the measured value comes from disjoint
findings. A Tier-1 external finding is still FindingOrigin::Deterministic
(it is not an AI finding), tagged ToolProvenance::Tier1External, and lands
at ConfidenceTier::Medium until benchmarked — never High by assumption.
Measure before defaulting on. No adapter is enabled by default without a measured union-lift of ≥ 0.05 at zero FP-on-fix regression on a proper corpus. As of the latest measurement pass, all four adapters remain opt-in; no default was flipped.
Tier 2 — borrow the host assistant's LLM
Pattern/AST/taint analysis is structurally blind to a whole class of vulnerabilities — the semantic long-tail:
- missing authorization checks (the absence of a call is invisible to taint)
- IDOR / broken object-level auth (data flows correctly; no missing sanitizer)
- business-logic flaws, insecure design, missing defense-in-depth
- intent admitted in comments/TODOs
These require reasoning about what the code is supposed to do. Tier 2
reaches them by borrowing the LLM the host AI assistant is already running
— via the MCP submit_ai_findings callback — rather than opening a new API
relationship. That is what makes it zero-marginal-cost and privacy-preserving
(see privacy.md).
Opt-in, default OFF
Tier 2 is off by default. It engages only when both of these
resolve true in .gadriel.toml (or the equivalent flags/env):
toml[tier2]enabled = false # default OFF — the on/off switchsource_consent = false # default OFF — privacy gate for sending source
Mechanically, [tier2] enabled is the switch: the MCP server only creates a
Tier2SessionRegistry when it resolves true. When off, validate_file /
validate_buffer emit tier2_request: null and never invite the host agent;
submit_ai_findings is still registered but is inert (it is backed by a
private registry that never receives a session). Resolution order for the
enable flag is --tier2 flag ▸ GADRIEL_TIER2 env ▸ [tier2].enabled ▸
false.
The default gadriel code scan is, and remains, 100% deterministic:
every finding carries finding_origin: deterministic with no tier2/ai_*
fields.
How it flows
validate_file (Tier-2 ON) ─▶ opens a session, returns tier2_request{session_id, chunks}
│
▼
host agent's LLM answers the semantic questions
│
▼
submit_ai_findings{session_id, ai_findings[]} ─▶ fuse() ─▶ FusionReport
Sessions are ephemeral: in-process only, one-shot consume, 120-second TTL.
The invariant: AI is advisory and can never weaken the gate
This is enforced as a type-level invariant in Rust, not a render flag
(the Finding::domain() invariant):
- Never-gate.
--fail-on, compliance KPIs, "Open Criticals", and OCSF emission count onlydeterministicanddeterministic + ai_corroboratedfindings. An AI-only finding isFindingDomain::AiAdvisory(non-gating) regardless of scan type or pillar — a rule cannot promote an AI finding into the gate. Every gate consumer excludes AI-only findings with no change toscan.rs. - Hard confidence floor. An
ai_advisoryfinding is floored toConfidenceTier::Lowat emission, so a "Critical" AI claim renders as Effective-Risk Low /unverified=trueeverywhere. - AI can never suppress a deterministic finding. A deterministic finding always survives and keeps gating; an AI refutation does not remove it.
The one bounded exception: SAST-Medium refutation
This narrows invariant 3 to a single, auditable case. A Tier-2
refutation may demote one business field — confidence_tier — and
only for a finding that is scan_type == Sast and
confidence_tier == Medium. It touches nothing else:
- it does not change
severity; - it does not set
FindingOrigin::AiAdvisory(so the type-level never-gate short-circuit never engages); - it does not remove the finding — the refuted finding stays in
findings.json/the report with a fullai_refutationaudit block.
The actual CodeVuln → CiConfig reclassification is produced by the
pre-existing gate reading the demoted value — no new gate is
added. If that gate is ever removed, refutation silently becomes a no-op
(fails closed), never a bypass. Low-confidence SAST is already non-gating, and
SAST never reaches High, so Medium is the only tier where a refutation can
change the gating outcome — and even there it is guarded by a rationale
signal-word check, a server-side eligibility re-check, and a hard
protected-true-positive zero-loss canary gate.
Why the split of labor
| Class | Structural reachability | Best tier |
|---|---|---|
| Memory safety (UAF, buffer overflow) | deterministic taint / clang | Tier 0 / Tier 1 |
| Injection with a known sink | pattern / AST / taint | Tier 0 |
| Missing authz, IDOR, business-logic | invisible to pattern/AST/taint | Tier 2 (advisory) |
| FP triage of existing findings | — | Tier 2 (corroborate/refute) |
Independent literature (mid-2026) puts LLM code-analysis at a >85% spurious
rate on real projects, which is exactly why the ai_advisory band is
labeled, floored to Low, never gated, and structurally filtered. The value is
delivery + grounding + correlation against the deterministic floor — not the
LLM in isolation.
