Tool Spec: rust-ai-native-conform-frontend — the Rust frontend for the language-neutral conform engine
01Status: SHIPPED with this package — crates/rust-ai-native-conform-frontend (id = "rust-syn") + crates/rust-ai-native-conform (binary rust-ai-native-conform), parsing .rs in-process with syn.
02Rust is the pilot language: go-ai-native-conform-frontend and typescript-ai-native-conform-frontend are projections of the discipline this frontend proved first. This document is the last of the three surface specs to be written, and it is written to the form its own projections established.
03The one structural difference from both projections: Rust needs no extractor sidecar. Go spawns a stdlib-only go-extract and TypeScript a node process, each over an NDJSON bridge, because neither language parses itself from inside a Rust binary. Rust does — syn is a library — so the fact producer is a function call, not a process, and the bridge protocol, the content-addressed materialisation of the extractor, and the degraded-file note that protects against a crashing sidecar all have no counterpart here.
1. The division of labour with the native Rust tooling
04req r1
05Rust's own toolchain carries the type / correctness half: cargo build (the compile gate), cargo clippy (the shipped lint census), cargo fmt --check, and the test suite as evidence providers.
06Those answer "is this well-typed and locally sane?" — the half the language does natively and well.
07This frontend answers the other half — the structural / architectural rules no Rust tool expresses:
- 08the cell-construction rule
R-001(flag-sites): a cell's<Type>::new(...)constructor appears only in the selection registry module ([rust] registry_file), mounted only when bothregistry_fileandregistry_gated_crateare set; R-002(cell isolation): a cell module imports seams and core only, never a sibling cell;unsafe-gate:unsafestays inside designated[rust] audit_cratesor under a fn-grain#[spec(deviates = …, reason = …)]testimony (B-025: markedDeviationAcknowledged, visible in the IR, never failing the gate — not suppressed);seam-has-doctest: every public seam — apubitem at a gated crate'ssrc/lib.rs, or apub traitanywhere undersrc/— carries a compiled doctest;pub-doctest: every public type seam (struct/enum/trait/union) in a[rust] gated_pub_doctestcrate carries a compiled doctest or a#[spec(documents)]edge;error-enum-cites-req: a thiserror enum in a gated crate carries a#[spec]REQ edge — the attribute half of the Class-F seam-error contract;error-message-cites-req: a thiserror variant's#[error("…")]Display text carries aspec://REQ — the message half of the same contract;cell-has-oracle: every#[cell]-manifested type is referenced by an integration test in its crate (the differential / characterization oracle);cell-name-is-computed: a cell's type name follows the computed convention (Pascal(variant) + seam, B-038);file-length: a source file over the rootmax_file_linesbudget (R3-003, position is a resource);invariant-comment-position: an invariant marker — a labeled, colon-bearing tag from the configured vocabulary, never a forceful word in prose — buried in the middle third of a long file, fed by the rootinvariant_comment_markers/invariant_comment_min_file_lineskeys;no-unwrap-in-domain:.unwrap()/.expect()stays out of domain logic (test scope and fn-grain deviations honored);ambient-env:std::env::{var,var_os,set_var,remove_var}reads stay at the composition root ([rust] env_roots) — an R-001 projection of the same flag-at-the-seam discipline;declared-test-matrices: a swept test matrix (a2^nbit-mask loop bound, or a ≥3-deep Cartesian nest of rangeforloops, in test context only) is generated, not declared (R-060);
09Where this list differs from the Go projection (measured, not copied from the twin): Go folds its domain bans into one RULE-BAN-CENSUS-AS-FACTS umbrella, where Rust splits them into the individual rules unsafe-gate, no-unwrap-in-domain, and ambient-env; Go carries the seam-error contract as one rule (go-seam-error-cites-req, both halves), where Rust splits it into error-enum-cites-req (the attribute half) and error-message-cites-req (the message half); and the deviation escape hatch is not a standalone rule here — it is honored as in_deviation testimony stamped on the UnsafeUse / UnwrapUse / EnvRead facts (B-025). The lint-suppression-needs-reason rule is wired into the Rust gate too, but it is fed by SARIF-ingested LintDiagnosis facts (foreign linters such as clippy), not by this frontend's facts — so it belongs to the T-sem citation path (§5), not to this list.
10Routing these through conform keeps one rule engine, one finding grammar, one ratchet baseline across all three languages, with the rules defined once over conform_core::Fact and fed by any frontend — a rule cannot drift between projections.
2. What the frontend is
11req r1
12A fact producer: parse a .rs file and emit the language-neutral fact stream the rules consume.
13In-process by construction — syn parses the file inside the same binary, so there is no subprocess, no materialised extractor, no wire protocol and no toolchain requirement beyond the one already compiling the project.
14An unparseable file yields zero facts and no panic — the same contract the sidecar frontends express as a degraded note, reached here by returning an empty vector.
- 15Fact kinds (the rust-syn vocabulary):
Item(a declared fn / struct / enum / trait with verbatimspec(...)/cell(...)/verifies(...)attribute text, thepubflag, and a doc-fence flag — the compiled-doctest candidate),Import(ausedeclaration: importing module → imported path),Ctor(a<Type>::new(...)construction site — the R-001 cell-construction signal),UnsafeUse(anunsafeblock /unsafe fn/ unsafe impl method, within_testand fn-grainin_deviationscope),ErrorVariant(a#[error("...")]thiserror variant with its owning enum's attribute text — the Class-F signal),FileMetrics(whole-file physical line count, one per parsed file — the file-length signal),UnwrapUse(a.unwrap()/.expect()call site, with test and deviation scope),EnvRead(astd::env::{var,var_os,set_var,remove_var}access site — the ambient-env signal, with test and deviation scope),InvariantComment(a comment carrying an invariant marker, from a raw-text scan —syndrops plain//comments so the AST cannot supply them), andTestSweep(a swept-matrix signal in test context only —bitmaskfor a2^nloop bound,nested-loopsfor a ≥3-deep Cartesian nest of rangeforloops). TheTsUnsafe/TsEnvRead/TsSeamError/GoUnsafe/GoConformancevariants belong to the other frontends and are never emitted here;LintDiagnosisarrives via SARIF ingest, not this frontend. - Rust parses twice, not once. Go's twin claims "one extraction, two consumers" (the conform frontend eats
facts, the specmap scanner eatsmarkers, one parser). That does NOT hold for Rust: the specmap scanner (core-ai-native-specmap::rscan::scan_source) runs its OWN independentsyn::parse_filepass over the same.rstext and emitsCodeItem/Edge/Warningfor the spec/code index, while this frontend'ssyn::parse_filepass emitsFacts for the rules. Two separate crates, two separatesynparses, two separate models — no shared parser, no shared vocabulary. The conformity Rust buys by parsing in-process everywhere does not extend to a single shared parse; the duplication is the recorded cost.
3. The frontend crate
16req r1
17rust-ai-native-conform-frontend implements the engine's Frontend trait: an id() of "rust-syn", a version() that bumps when the fact schema grows (retiring cache slots wholesale), and an extract(file, package, module, text) -> Vec<Fact> that parses in-process.
18The frontend version is a cache key, never a release number. It is at "10" (frontend v10), and each bump retires every cached fact slot keyed by the old value. Each bump is a change to what the extraction EMITS — a new fact variant or field added (the history adds ErrorVariant, FileMetrics/UnwrapUse, UnsafeUse, EnvRead, InvariantComment, TestSweep, and the is_pub/has_doctest fields), or the scope of an existing fact narrowed (the marker vocabulary shrank to five labeled tags in v8; the nested-loop signal narrowed to range iterables in v10) — so the cached fact set from the old extraction no longer matches and must retire wholesale.
19Facts are keyed (file content-hash, frontend id+version) in the engine's content-addressed store — a 1-file diff re-extracts 1 file.
4. Topology: the [rust] policy section
20req r1
21conform.toml gains a [rust] section, the ten keys of the engine's RustConfig: roots (source roots to scan; a <dir>/* entry expands each subdirectory as one crate, any other entry is a literal crate dir; default ["crates/*"]), exclude_substrings (a file whose repo-relative path contains any of these is skipped; default ["/generated/"]), gated (the crates the Class-F/G gates apply to — the unit list, since Rust's gate unit is the crate; default []), [[rust.exempt]] {unit, reason} (crates deliberately outside gated, each with a recorded reason; default []), gated_pub_doctest (crates whose whole public type surface is gated for doctests; default []), audit_crates (designated audit crates, exempt wholesale from the unsafe and ambient-env gates; default []), env_roots (repo-relative files where reading the ambient environment is sanctioned; default []), registry_file (the one legal cell-construction site, R-001; None disables R-001; default None), registry_gated_crate (the crate R-001 gates, meaningful only with registry_file; default None), and [[rust.floor_disable]] {step, reason} (floor steps this project disables, each with a recorded reason; default []).
22The language-neutral keys sit at the root of conform.toml, NOT under [rust], because they model no language. The Rust rules read four: max_file_lines feeds file-length; invariant_comment_markers / invariant_comment_min_file_lines feed invariant-comment-position; and sarif_reports feeds the SARIF ingest whose LintDiagnosis facts the lint-suppression-needs-reason rule cites (the T-sem citation path, §5). What each root key means, and what it defaults to, is described once, in ENGINE-CONFORM §6 (the policy file) — this surface names which ones the Rust rules read, not their values. The per-language tables [rust] / [typescript] / [go] are root keys too, each owning its own section; and nine retired flat root keys (roots, exclude_substrings, gated_crates, gated_pub_doctest, audit_crates, env_roots, registry_file, registry_gated_crate, exempt) are now loud tombstones — their presence parses but Config::load rejects each with a targeted move hint, never serde's generic unknown-field error.
23The every-unit-gated-or-exempt invariant is enforced by the engine on every check, exactly as for the sibling stacks — Rust's gate unit is the crate (Go's is the package, TypeScript's the cell — the B-029 ruling), so [rust] gated / [[rust.exempt]] is the expand-as-you-conform ratchet over crates.
5. The honest note
24The structural gate is only as good as its facts, and syn is a PARSER, not a type checker: it sees syntax and attributes, not resolved types.
25Rules that would need type information (e.g. "this call's receiver is a seam type") are out of this tool's scope.
26The division is deliberate — the same three-tier split (T-lex / T-syn / T-sem) ENGINE-CONFORM §1 defines; this frontend is the T-syn tier.
27Rust's T-sem tier is not empty, and the gap is narrower than "no vehicle". By the Go twin's own framing — "Go's T-sem tier is the toolchain itself" (vet, staticcheck, exhaustive) — the toolchain's lint engine IS the T-sem tier; Rust's toolchain lint engine is clippy, the direct parity analogue. And where the sibling stacks merely list their toolchain linters as evidence providers in prose, this engine reads clippy's verdicts back IN as Fact::LintDiagnosis via SARIF ingest (B-026: the root sarif_reports key, sarif::load_reports), so a Discipline rule may CITE a clippy diagnosis as the evidence for its own claim (Fact::cites_lint, the lint-suppression-needs-reason rule) — the deepest T-sem wiring of the three stacks, not an empty one. The genuine, narrower gap the draft was reaching for: there is no DISCIPLINE-AUTHORED type-aware lint library — no dylint-class vehicle carrying Discipline-specific type rules (none ships in this stack, which pins stable Rust 1.93 / edition 2024) — so the type-dependent rules the T-syn frontend cannot express (it sees syntax, not resolved types) stay out of the tool's scope. That gap is recorded rather than silent, and the grammar such a vehicle would speak (violates REQ …; fix surface: …) is already spoken by the conform rules themselves — and, through the citation path, by an ingested clippy diagnosis.
6. What this document does NOT cover
28The rules' semantics live in ENGINE-CONFORM, one definition for all three languages; this document covers only what the RUST side reads, emits and configures.
29GUIDE-AI-NATIVE-RUST.xml is the author-facing document — how to write code that passes; this one is the surface: what the tool reads and what it accepts.