VibeVM
Contents
On this page
en
Publisher
org.vibevm.ai-native
Version
1.0.0latest
Audiences
Reading time
10 min
Rendered
Read aloud
never

ENGINE — conform: the cross-language conformance engine, v0.1

01Status. Design, beta.

02Implements the Charter's T3 decision: our own engine, borrowed frontends.

03Rationale (owner-decided): asking a compiler about its own language is one page of code; rebuilding name resolution over tree-sitter is weeks.

04The balance is made explicit through escalation tiers, not ad-hoc judgment. Specified, not built: the whole tier vocabulary of §1 is unimplemented. There is no Tier type, no tier field and no escalation path in core-ai-native-conform or in any frontend crate, stack CLI or host driver; T-lex / T-sem appear nowhere in the tree and T-syn only inside three doc comments that quote this document. What is actually explicit is the frontend choice, made by the caller at the call site — which is a different mechanism, and an ad-hoc one.

05Derives from.

  • 06Charter A1 (findings carry explanation chains),
  • A2 (extraction is cached, incremental),
  • A3 (conformance is below the algorithmic floor — no LLM in the checking path),
  • A5 (rules ship with checkers).

1. Escalation tiers

07Every rule record declares tier — the minimum analysis depth its check requires. Specified, not built: no rule record declares a tier, because a rule record has no such field. The shipped contract is pub trait Rule { fn id(&self) -> &'static str; fn why(&self) -> &'static str; fn check(&self, facts: &[SourceFacts]) -> Vec<Finding>; } (conform/src/finding.rs:53-57) — three methods, none of them tier. All fifteen shipped rules take whatever facts they are handed.

08The engine runs the cheapest adequate frontend; escalation is declared, never improvised. Specified, not built: the engine performs no selection, so it can neither pick the cheapest nor escalate. store.rs:95-118 exposes one entry point per language, each taking a caller-supplied &dyn Frontend — the choice is made outside the engine, by whoever calls it, which is precisely the improvisation this sentence rules out.

09
Tier Capability Backend Cost
T-lex textual invariants (forbidden tokens, file layout, license headers) ripgrep-class scan (MIT/Unlicense) — Specified, not built: this tier has no implementation and no backend. No ripgrep-class scanner is wired into the engine, and no forbidden-token, file-layout or license-header rule exists in the roster (rules/mod.rs:21-25). ~free
T-syn structure: items, attributes, imports, spans, hashes @status:impl/done tree-sitter (MIT) universal; syn (MIT/Apache-2.0) for Rust precision — Half built: the syn half is real and running (rust-ai-native-conform-frontend, whose own module doc calls itself "the Rust T-syn frontend"). The universal tree-sitter backend does not existtree-sitter / tree_sitter return no hit in any crate or manifest in the repository, so there is no universal path and each language got a bespoke frontend instead. The capability column is accurate; the tier that would name it is not. cheap, incremental @status:impl/done
T-sem types, name resolution, macro/template expansion, real import graphs per-language compiler frontend (§2) — Partly built, but not as a tier: one real compiler frontend ships. typescript-ai-native-conform-frontend reaches the TypeScript Compiler API through the packaged tools/ts-extract sidecar, and the Go stack drives gopls as an LSP oracle in go-ai-native-tcg. Neither is reachable as a tier: both are plain Frontend implementations a caller names directly, there is nothing to escalate from, and no rule declares it needs them. expensive; cached hard

10Rule examples:

  • 11R-021 forbidden-idiom scan → T-lex/T-syn; Specified, not built (→ B-038): R-021 is not a rule in this engine. R-021 returns zero hits across every crate in the repository, and no forbidden-idiom scan of any kind ships. It is cited as a ban in the language guides and authored nowhere.
  • R-002 import-graph isolation → T-syn (Rust) / T-sem (C++ where headers lie); The rule is real; the mapping is not. CellIsolation carries id "R-002" (conform/src/rules/structure.rs:77-91) and is projected per language as TsCellIsolation and GoCellIsolation. What does not exist is either side of the arrow — no tier assigns it a depth, and there is no C++ frontend to escalate to.
  • R-020 naming-vs-manifest → T-syn + specmap index; Specified, not built (→ B-038): neither the rule nor the join. R-020 returns zero hits across every crate, and the conform engine does not depend on the specmap crate at all — no manifest lists it — so a rule combining structural facts with the specmap index cannot be written today without a new dependency edge.
  • type-flow rules (future) → T-sem.

2. Frontends — borrowed, behind one trait

12trait Frontend {
    fn lang(&self) -> Lang;
    fn tier(&self) -> Tier;
    fn extract(&self, files: &[SourceFile]) -> Result<Vec<Fact>, FrontendError>;
}
13
Lang T-syn T-sem License posture
Rust syn in-process rust-analyzer crates or rustc_driver (nightly caveat) — Specified, not built: the T-sem column names software that is absent. rust-analyzer, rustc_driver, ra_ap and hir return no hit in the engine, in rust-ai-native-conform-frontend, or in any manifest. The T-syn column is exact and running. @status:spec/done MIT/Apache-2.0 — clean
C++ — Specified, not built, in full: there is no C++ frontend at either depth. tree-sitter, tree-sitter-cpp, libclang and clang-sys return no hit anywhere in the repository, no C++ crate exists in any workspace, and the Fact model (conform/src/facts.rs:25) carries no C++ variant. This row is a design intention; the three language stacks that ship are Rust, TypeScript and Go. tree-sitter-cpp libclang via clang-sys — the one-page-AST path Apache-2.0 w/ LLVM exception — clean
TS/JS tree-sitter / SWC (Apache-2.0) — Specified, not built, and ruled a non-build: neither tree-sitter nor SWC returns a hit anywhere in the repository, and the B-023 study (2026-08-03/04) recommends never building one — the shipped sidecar already parses at this depth (next cell), so a second syntactic parser would be a duplicate in any outcome. @status:spec/done TypeScript compiler API via a Node sidecar processBuilt, at parser depth: tools/ts-extract's extract.ts drives the Compiler API's parser surface only — createSourceFile, forEachChild, createScanner, the JSDoc readers — and never constructs a Program or calls getTypeChecker, so no type-level fact is extracted and the cell's implied T-sem is in substance T-syn (B-023 evidence + the owner's counter-probe). The checker deepening of this same sidecar is deferred by the owner's ruling, 2026-08-04, verbatim: «давай B-023 отложим до тех пор, пока не появится ещё какое-то правило кроме "as_cross с не локальной областью". Не нужно забывать об этом, это нормальное продолжение развития, просто это кандидат на середину или конец бэклога» — it waits for a second type-requiring rule. @status:spec/done Apache-2.0 — clean
Go go/parser+go/ast via a stdlib-only go run sidecar (go-extract) gopls / go vet as evidence providers — Built, but at another layer, and not as evidence providers. Both tools run: go vet ./... is step 2 of the Go floor (go-ai-native-cli/src/floor.rs:115-120) and gopls is driven as a long-lived LSP oracle by go-ai-native-tcg. Neither reaches conform — conform ingests no output from either, so nothing they know becomes a fact. Read this cell as naming where semantic depth lives in the Go stack, not as a conform frontend. @status:spec/done BSD-3 — clean
Python — Specified, not built, in full: there is no Python frontend at either depth. rustpython, symtable, cpython and any Python sidecar return no hit in the engine, its manifests or any frontend crate, and the Fact model carries no Python variant. The B-023 study (2026-08-03/04) finds both columns feasible on the ready sidecar precedent (go-extract / ts-extract) and prefers the CPython sidecar over an in-process RustPython parser — but the frontend has no consumer: no python-ai-native-lang package exists, so building it now would be dead engine code. It waits on a product decision about a Python stack (B-023's disposition, owner-ruled 2026-08-04). RustPython parser (MIT) in-process CPython ast/symtable via sidecar PSF / MIT — clean

14The tier vocabulary (document taxonomy). T-lex / T-syn / T-sem name the depth an enforcement mechanism reads at: T-lex — lexical (tokens, pragmas, build tags; no parse), T-syn — syntactic (parser-grade extraction), T-sem — semantic (compiler/type-grade). The taxonomy classifies rules and frontends in prose and tables; it is deliberately not (yet) a code-level type — the rule registry's tier column is a later-phase mechanism.

15Sidecar protocol: newline-delimited JSON over stdio, versioned; sidecars emit Facts, nothing else.

16A frontend whose toolchain is broken is a hard error: each stack's driver probes its frontend before extraction and fails the run (typescript-ai-native-conform/src/lib.rs:66-70), so the gate can never report green over zero facts. A per-file extraction failure surfaces on stderr and yields an empty fact set for that file. Specified, not built: there is no skipped (frontend unavailable) report status — Finding carries no status field.

17Foreign linters as evidence providers. clippy, eslint, ruff, clang-tidy run as-is; their output is ingested as facts via SARIF (the OASIS static-analysis interchange format).

18We neither reimplement their checks nor fork them — we cite them: a Discipline rule may be check: { tool: "clippy", id: "...", status: enforced }, and conform's job is orchestration + the checks no generic linter can know (manifest-vs-name, specmap coverage, cell isolation). The posture holds; the record shape and two of the three examples do not. Foreign linters really are run as-is and never reforked — the floor shells out to cargo clippy, go vet, staticcheck, exhaustive, prettier, tsc and eslint. But the citation is a floor step, not a rule field: the check: { tool, id, status } shape returns zero hits across the engine, so no rule can cite a linter finding. Of the three checks named as conform's own, cell isolation ships (R-002); manifest-vs-name does not (R-020 is unauthored) and specmap coverage cannot, since conform does not depend on the specmap crate. Orchestration, too, lives one layer up in each stack's floor, not in the engine.

3. The fact store

19Language-neutral normalized facts; this is the ledger's "facts class" (LEDGER §3) instantiated:

20{ "fact": "item",   "lang": "rust", "path": "crates/vibe-resolver/src/naive.rs",
  "kind": "struct", "symbol": "vibe_resolver::naive::NaiveDepSolver",
  "span": [29, 41], "hash": "sha256:…", "attrs": ["spec(implements=…, r=2)"] }
{ "fact": "import", "from": "vibe_resolver::naive", "to": "vibe_core::manifest" }
{ "fact": "flag_read", "symbol": "…", "site": "crates/vibe-cli/src/registry.rs:88" }
  • 21Key: (file content-hash, frontend id+version). Facts never rot semantically — re-extraction happens only when the file or the frontend changes. This is what makes conformance incremental: a 1-file diff re-extracts 1 file.
  • Store: content-addressed, local + CI-cache; never committed (derived data with a deterministic producer).

4. Rules as queries

22v0.1: rules are Rust implementations of one trait — fn check(&self, facts: &[SourceFacts]) -> Vec<Finding>, alongside fn id() and fn why() (core-ai-native-conform/src/finding.rs:51-56) — compiled into the engine. The earlier sketch's specmap: &Index parameter is dropped: the conform crate carries no dependency on the specmap crate, so the parameter was impossible, not merely absent.

23A declarative query DSL (datalog-flavored) is deliberately deferred: we will know its right shape after ~30 real rules exist, not before (Open Question 2).

24Findings carry the A1 chain: rule id → why (axiom trace) → span → involved facts → deviation status (a matching deviates record downgrades the finding to deviation-acknowledged).

25Output: SARIF (so IDEs and CI render findings for free) + the ratchet baseline file (conform-baseline.json): pre-existing findings are frozen per scope; new ones fail the gate; the baseline only shrinks.

5. Determinism and gates

26Same inputs → byte-identical SARIF (stable ordering, no wall-clock).

27Tested the way vibevm tests its resolver and codegen: run twice, diff.

28Gate command: conform check --baseline conform-baseline.json --scope crates/vibe-resolver — exit code is the acceptance criterion the Playbook relies on; no human judgment in the loop (A3).

6. The policy file — where a key lives, and why

29req r1

30The gate is config-driven: conform.toml at the project root is the consumer's policy, and this engine owns its schema. The split is PROP-024's — the policy stays with the consumer, the engine ships in the package — so a project decides which crates are gated without forking a rule, and no project can invent a key the engine does not define.

31The rule that places a key: a key sits at the ROOT when it models no language, and inside a [<language>] section when it does. The language sections are homogeneous by construction — each owns the same roots / gated / exempt shape over its own unit — while a key the engine reads identically whatever produced the facts has no business being written three times.

32The root keys are described here, once. max_file_lines — the per-file line budget every frontend feeds (default 600). invariant_comment_markers — the invariant-marker vocabulary, the labeled colon-bearing tags; empty disables the rule that reads it. invariant_comment_min_file_lines — the file length below which that rule stays silent, because on a short file "thirds" mean nothing (default 120). sarif_reports — where a floor step deposits foreign-linter SARIF reports for the gate to read back in; one deposit point for every linter, since each report names its own tool and the engine never needs to know which language it concerns. Beside them sit the per-language tables [rust], [typescript] and [go], each described by its own stack's conform-surface document.

33A stack's conform-surface document names which root keys its rules read and cites this section for what they mean — it does not restate their defaults. Three surface documents restating one default is three writers for one truth, and the third copy is where it goes stale. The per-language section is the surface document's own to describe in full, because there the key and its meaning genuinely belong to that stack.

34A key that moves is retired as a loud tombstone, never deleted. Nine flat root keys moved into [rust] and each remains declared, typed to accept any shape, so its presence is caught and rejected with a targeted move hint rather than serde's generic unknown-field error. A silently-ignored stale key is a project believing it is gated when it is not — the same disease as a dead exclusion, and the same cure: say the name out loud.

7. Open questions

  1. 35rust-analyzer crates vs rustc_driver for Rust T-sem (stability vs fidelity) — decide when the first T-sem Rust rule actually lands; none of the Phase ≤4 checks need it.
  2. Query DSL: shape and whether rules become data (loadable rule-packs) — after 30 in-tree rules.
  3. Fact schema versioning across frontend upgrades — proposal: schema carries v, store segregates by version, mixed reads forbidden.
  4. Performance envelope targets (full-workspace cold scan budget; warm incremental budget) — set from Phase 4 measurements.

36Any frontend or tier specified here that is not exercised by Playbook Phase 4 is either removed from this document or annotated in place as specified, not built — never carried as unmarked aspiration.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.ai-native/core-ai-native@1.0.0/mechanisms/ENGINE-CONFORM-v0.1

.md.xmlllms.txt