# The gate grows per-language units — config surface, coverage invariant, flag rule {#root}

@status:spec/done

[p01] @fact:companion-line **Companion to:** BACKLOG.md B-029 (closed by `1ef63a37`) (neutral/per-language gate key + the config-surface enrichment, ruling 2.1 recorded in the row), B-034 (closed by `f882cd46`) (gated-or-exempt for Go/TS), B-039 (closed by `1f048058`) (mount R-001 on the TS gate); the fork it carries is [`TOOLING-MAP.md` §5 №2](../../TOOLING-MAP.md#forks). Evidence: [`e8-r1-config-census.md`](../../campaigns/packages-2026-09/harvest/e8-r1-config-census.md) (the config surface), [`e8-r2-gate-units-census.md`](../../campaigns/packages-2026-09/harvest/e8-r2-gate-units-census.md) (units, FlagSites, rosters). Non-normative; the PROPs and the backlog rulings win. @status:spec/work

## 1. The measured basis, in five lines {#basis}

[p02] @fact:basis-summary (1) Only Rust has a gate-unit notion: `validate_against_tree` classifies cargo-crate dirs against `gated_crates`/`[[exempt]]`, called only by the Rust driver (census R1 Q4). (2) Go and TS scan `[go]`/`[typescript]` `roots` into a flat file list — no unit enumerator, no lists, no invariant (R2 Q1–Q2). (3) An empty/mis-scoped per-language `roots` inside a present `conform.toml` is a **silent green** for Go/TS; Rust is guarded by `warn_vacuously_gated` + the count summary (R1 Q8, R2 Q3). (4) The literal FlagSites mount on TS is impossible today: the rule keys on `Fact::Ctor` and `cell(…)`-attributed items, both emitted only by the Rust frontend; TS lacks the facts, the config field, and the cell-type notion (R2 Q4). (5) The migration surface of the root keys is tiny — three live `gated_crates` carriers, all Rust projects; Go/TS carriers: zero (R1 Q5–Q6). @status:spec/work

## 2. The fork — RULED by the owner (map §5 №2, 2026-08-04) {#fork}

[p03] @fact:fork-ruling-units **The units, ruled.** On the presentation the owner confirmed the Rust unit and picked Go's by analogy, verbatim: «А что является единицей учета в Rust? Крейты? Если да, давай в Go сделаем пакеты» — Rust's unit IS the crate (census R1 Q4), so **Go = package**. For TS his conditional («А в Расте как сделано сейчас? Ячейка? Если да, давай делать ячейку») does not resolve literally — Rust's unit is the crate, not the cell — and his meta-directive below governs instead: the norm is **each language's own native unit** — the crate is Rust's, the package is Go's, and TS, having no crate/package analogue, contributes the discipline's own native TS unit — **the cell**. Non-cell dirs under the TS roots stay outside unit-accounting by the discipline's own boundary (cells are the domain), recorded, not silent. @status:spec/work

[p04] @fact:fork-ruling-homes **The homes, ruled by quality bar.** On the third half the owner delegated the pick with an explicit bar, verbatim: «Какое решение максимально хорошо с точки зрения построения систем, расширяемо на новые языки (скоро добавится Python!) и вообще круто? Я не хочу делать плохие временные решения только потому что так было проще и это экономит какие-то незначительные рефакторинги. Хочется сделать хорошо и надолго. И это относится к предыдущим двум вопросам тоже». Under that bar the flat-Rust-root option dies (it is exactly the historical asymmetry that compounds per added language) and the answer is **full symmetry, one section shape, N languages**: every language a section, the root table carrying only the genuinely cross-language budget. **Python is coming** — a recorded planning fact; it lands as one more section with zero new vocabulary decisions. @status:spec/work

[p05] @fact:fork-shape **The uniform section shape (the systemic answer).** Every language section carries the same five-slot shape — `roots`, `exclude_substrings`, `gated` (the unit list), `[[<lang>.exempt]] {unit, reason}`, `floor_disable` where the language's floor supports disablement — plus its own language-specific extras (`[rust]`: `gated_pub_doctest`, `audit_crates`, `env_roots`, `registry_file`, `registry_gated_crate`; `[go]`: `cells_dir`, `seams_pkg`, `registry_pkg`; `[typescript]`: `cells_dir`, `seam`). The gate key is the **neutral spelling `gated` in an idiomatic home**: the section names the language, the unit noun lives in each language's docs and error strings — no foreign term in any config (B-029's origin), no per-language alias sprawl, and the N+1th language adds a section, never a vocabulary debate. The root table keeps `max_file_lines` (read by all frontends) and nothing else. @status:spec/work

[p06] @fact:fork-legacy **The old root keys die loudly with the hint.** The root container keeps `deny_unknown_fields`; the retired flat keys (`roots`, `exclude_substrings`, `gated_crates`, `gated_pub_doctest`, `audit_crates`, `env_roots`, `registry_file`, `registry_gated_crate`, `[[exempt]]`) are declared as tombstone fields whose presence is a targeted parse-time error naming the move (`gated_crates` → `[rust] gated`) — the house pattern (B-031's loud death with a rename hint), not serde's generic unknown-field message. Migration surface, measured: three in-tree carriers (host, fractality package, rust-demo) plus the Rust init template; zero external Go/TS carriers; pre-publication, so the break ships inside the refactor window. @status:spec/work

## 3. The design past the fork (boss-settled, worker-elaborated) {#design}

[p07] @fact:design-config-v2 **The Config restructure (the symmetric surface).** The engine's `Config` becomes root = `max_file_lines` + three (later N) language sections; `RustConfig` is extracted to the uniform shape and the Rust-specific extras move inside it; the retired flat keys become loud tombstones per `#fork-legacy`. `Store::at_repo` re-points to the `[rust]` view (rename to the `for_*` symmetry at the worker's latitude). The exempt entry is one shared struct `{unit, reason}` across languages. @status:spec/work

[p08] @fact:design-invariant **The coverage invariant (B-034).** The engine grows one generic core — enumerate units, require `gated ∪ exempt` coverage, reject duplicates/both-listed/empty reasons/ghost entries — parameterised by the unit noun so each language's six error strings speak its own word (crate/package/cell) and name the new keys (`[rust] gated`, never the retired spelling). `validate_against_tree` becomes the Rust-shaped wrapper over it, behaviourally stable. New per-language enumerators: Go — walk `[go].roots` for `.go`-bearing dirs (the excludes of the scanner apply, so the unit set matches the scanned corpus); TS — list `cells_dir`'s immediate subdirs. The Go and TS drivers call their validator in `run_check` + `run_freeze` exactly where Rust does. @status:spec/work

[p09] @fact:design-vacuous **The vacuous-green guards.** Both drivers gain the two announce mechanisms Rust already has: a per-language `warn_vacuously_gated` (a gated unit the scan attributed zero files to) and the gated/exempt count summary; plus the sharper one the census showed missing everywhere it matters — a present config whose language `roots` enumerate **zero units** warns loudly instead of passing silently. @status:spec/work

[p10] @fact:design-flag-rule **The TS flag rule (B-039), TS-shaped.** The literal R-001 mount being impossible (census R2 Q4), the rule is built in the TS frontend's own shape, enforcing the guide's own promise («flags read once at the composition root; no config reads in domain cells»): `ts-extract` emits a new census kind for environment/config reads (`process.env`, `import.meta.env`); a new engine rule flags such reads in files under `cells_dir` outside the configured composition root; `[typescript]` gains the root-naming field. The `if (flag)` half of the promise needs flag identity that no table provides yet — recorded as the rule's documented limit, not silently claimed. The demo instantiates the tier it currently only describes (a typed registry + composition root in `research/ts-demo`), so the guide's «described, never instantiated» note dies honestly. @status:spec/work

[p11] @fact:design-go-registry **The Go half of B-039 routes to batch 2.** The census found Go promising R-001 at the seam with a dead config key (`registry_pkg`, zero readers) and no rule; a `registry is the sole cell importer` rule is implementable today from existing import facts. It is the natural sibling of B-033 (the dedicated Go seam-error rule) and rides that batch rather than widening this one. @status:spec/work

[p12] @fact:design-docs **The idiom lands in the carriers (ruling 2.1's second half).** The Go/TS frontend docs, guides, skills and init templates speak the new keys from birth; the Go guide's «one spelling across the language stacks today» caveat and the frontend doc's apology die in the same change. Init templates mirror the Rust adoption posture (worker verifies what `rust-ai-native init` actually emits and mirrors it per language). @status:spec/work

[p13] @fact:design-not-built **Not built here:** no per-language `max_file_lines` override (no measured need); no transition period accepting both spellings (three in-tree carriers, pre-publication — the loud tombstone is the whole migration aid); no foreign-linter ingest (B-026); no assertion-presence rules (B-030, batch 2); the Go sole-importer rule rides batch 2 (`#design-go-registry`). @status:spec/work

## 4. The build cut (re-cut on the ruling) {#cut}

[p14] @fact:cut-w1 **W1 — one thread, engine only:** the Config v2 restructure (`RustConfig`, uniform `gated`/`exempt {unit, reason}`, legacy tombstones with move hints), the generic validator + three enumerators + three wrappers, vacuous helpers for all three languages, `Store` re-point, engine tests — canonical v0.8.0 only; self-verify is the canonical engine's own check/test/clippy/budget. The boss tail then fans the engine out (sync-engines ×6, vibedeps rematerialise) and migrates the three config carriers + the host xtask expectations in the same atomic landing. @status:spec/work

[p15] @fact:cut-w2 **W2a ∥ W2b — after W1 lands, two lanes on disjoint packages:** *W2a (rust):* the Rust FE re-points to `config.rust.*`, CLI `init`/`health` follow, rust-demo policy migrates, package tests green. *W2b (go+ts):* the Go/TS drivers wire their validators + vacuous guards into `run_check`/`run_freeze`, `init` templates emit the uniform shape, demo policies gain their unit lists, driver-run fixture configs mapped and migrated, package tests green. @status:spec/work

[p16] @fact:cut-w3 **W3 — doc packet, after W2, disjoint files:** the carriers sweep (frontend docs, guides, skills, READMEs) onto the new keys, the unit-per-language norm, and the death of the «one spelling across stacks» apology. @status:spec/work

[p17] @fact:cut-w4 **W4 — the TS flag rule, after W2b:** `ts-extract` census kind → engine rule → `[typescript]` root-naming field → TS driver mount → demo registry + composition root → guide re-point. @status:spec/work

[p18] @fact:cut-boss-tail **The boss tail per slice:** review by WORKER-REPORT, `cargo xtask sync-engines` from the host root, fmt per package manifests, panel bare with the tail read, rematerialise after canonical package edits, commits per Rules 1–4. After the batch: the B-035 loop pass re-runs the parity table and the F-185 family re-judgement drains through mirror → merge-verdicts → seal. @status:spec/work

## 5. Acceptance {#acceptance}

[p19] @fact:acceptance-list Measured, not narrated: (1) the Go and TS drivers fail loudly on an unclassified unit and on ghost entries, six messages per language in the language's own noun; (2) an empty-scope run warns instead of passing silently — exhibited on a scratch tree; (3) the TS flag rule flags an env read inside a cell and stays quiet at the composition root — exhibited on the demo; (4) the three migrated carriers parse in the v2 shape and a file carrying any retired flat key fails with the targeted move hint — exhibited; (5) the panel green with the tail read; (6) F-185's re-judgement rides the landing. @status:spec/work

