tensor-grep architecture contract
What this is. A ground-truthed map of tensor-grep's load-bearing design: the invariants a change must not break, and the weak points you must not oversell. Read it to understand why the code is shaped this way before you touch it. It is not a how-to — for that, hand off to a sibling (routing table below).
What tensor-grep is (as of 2026-07-24, v1.95.0, pyproject.toml): a code-intelligence CLI named tg. A Rust core (rust_core/ — both a PyO3 extension and a standalone tg binary) plus a Python CLI (src/tensor_grep/). Apache-2.0. Ships to PyPI (package tensor-grep), npm, Homebrew, winget. CONTRIBUTING.md calls it a "benchmark-governed, contract-heavy codebase" — that is the whole point: the contracts below are enforced by tests and a CI gate, not by convention.
When to use this skill vs a sibling
| You are about to… |
Use |
| Understand why the front door / routing / backend contract exists (this skill) |
you are here |
| Add/rename a command or a search flag; ship a change safely |
tensor-grep-change-control |
| Debug a live misroute, hang, wrong-result, or "no matches that should match" |
tensor-grep-debugging-playbook |
| Study a settled past failure so you don't re-fight it |
tensor-grep-failure-archaeology |
Use tg as a user (search/orient/callers/agent flags) |
code-search-and-retrieval-reference, or the .claude/skills/tensor-grep/ usage skill |
| Set/override config or env axes |
tensor-grep-config-and-flags |
| Build the Rust ext / set up the toolchain |
tensor-grep-build-and-env |
Run diagnostics (doctor, dogfood, readiness) |
tensor-grep-diagnostics-and-tooling |
| Make or defend a speed/quality claim with numbers |
tensor-grep-benchmark-and-proof-toolkit |
| Position the product / write release notes |
tensor-grep-release-and-positioning |
Do not use this skill to authorize a change. It explains the design; it does not route around tensor-grep-change-control or the project's PR/council/dogfood discipline. Any code change still goes through change-control.
Jargon (defined once)
- Front door / bootstrap — the process entry point
tensor_grep.cli.bootstrap:main_entry that sees raw argv before the Typer app.
- Typer app — the Python click/Typer CLI in
src/tensor_grep/cli/main.py (@app.command functions). It is the inner CLI, not the front door.
- Native binary / native front door — the standalone Rust
tg binary built from rust_core/. Fast path for search routing.
- Sidecar — Python doing work the native binary bounces to it (
TG_SIDECAR_PYTHON).
- CliRunner — Typer's in-process test harness. It calls the Typer app directly and bypasses the bootstrap front door — the single most important test-coverage caveat in this repo.
- rg — ripgrep. ast-grep — structural (AST) search. Both are baselines tg is measured against, not beaten.
- Capsule — the Actionable Context Capsule emitted by
tg agent (capsule_version = 1).
The front door: intercept before Typer
tg is not "a Typer app." The published entry point is bootstrap.main_entry — grep -n "^def main_entry" src/tensor_grep/cli/bootstrap.py (was :1444, now :1550; this anchor has drifted three times, never cite the number as fact). It parses argv itself and, for a plain text search, forwards to the native tg binary or to ripgrep before Typer ever runs (re-grep _run_rg_passthrough — the dispatch runs from the _normalize_search_invocation call through the final raise SystemExit(_run_rg_passthrough(...)); traced live 2026-07-29 and confirmed a bare tg search PAT exits THERE, never reaching Typer). The Typer app is only reached for TG-only flags, help, or commands that require full CLI (grep -n "^def _requires_full_cli" src/tensor_grep/cli/bootstrap.py — :480, unchanged this pass).
Why this matters, concretely:
- CliRunner cannot see routing bugs. It invokes the Typer app directly, so any bug in
bootstrap routing (a flag that leaks to rg, a fork-bomb delegation loop, a wrong native/Python choice) is invisible to CliRunner tests and green in CI while broken for real users. This is exactly how the --rank plain-text crash shipped (AGENTS.md §"Dogfood the Real Binary, Not CliRunner" — grep -n "^## Dogfood the Real Binary" — was :422, now :957). Rule: verify front-door behavior against the REAL published binary via scripts/dogfood/ (Dockerfile + dogfood_features.py), never CliRunner alone.
- Two mutual-delegation fork-bomb hazards are guarded, not theoretical.
TG_REEXEC_GUARD (grep -n 'os.environ.get("TG_REEXEC_GUARD")' src/tensor_grep/cli/bootstrap.py — was :1497, now :1617) stops native→python→native search loops; _json_aggregate_blocks_passthrough (grep -n "^def _json_aggregate_blocks_passthrough" src/tensor_grep/cli/bootstrap.py — :572, unchanged this pass) stops --json + a render-only flag (e.g. -b) from deadlocking the native front door; _run_requires_ast_workflow (grep -n "^def _run_requires_ast_workflow" src/tensor_grep/cli/bootstrap.py — was :1385, now :1491) keeps tg run --selector/--strictness/--stdin/--globs in Python so it does not ping-pong. If you touch delegation, you can re-arm a fork bomb — see tensor-grep-failure-archaeology.
Native-vs-Python routing (the decision tree)
Search routing is a single shared decision in rust_core/src/routing.rs::route_search(...) (documented in docs/routing_policy.md). It returns a RoutingDecision carrying selection, routing_backend, routing_reason, sidecar_used, allow_rg_fallback. Priority order (routing_policy.md §"Unified tg search decision tree"):
--index → TrigramIndex (highest override)
--gpu-device-ids → NativeGpuBackend (overrides warm-index + size routing; must fail loud if unhonorable)
--force-cpu/--cpu with structured output or no usable rg → NativeCpuBackend
- AST command →
AstBackend
- Warm non-stale compatible
.tg_index → TrigramIndex
- corpus > calibrated threshold and GPU available and calibration positive →
NativeGpuBackend
- else, plain-text request ADMITTED by
native_can_serve_plain_text → NativeCpuBackend (routing_reason = plain-text-native; skips the rg spawn entirely — see the clause bullet below)
- else,
rg available and no structured output → RipgrepBackend
- else →
NativeCpuBackend
- native CPU route fails and
allow_rg_fallback → RipgrepBackend final fallback
Load-bearing consequences:
rg is the normal cold-path backend when installed. Native CPU is the default only for structured output (--json/--ndjson), explicit --cpu, warm index, AST, and GPU fallback. Do not "optimize" tg to beat rg on cold text — that is the parity tier (see Known-Weak §3).
- Warm-index auto-routing is gated: pattern ≥ 3 bytes, no
-v, -C, --max-count, -w, -g, and the cache must exist + be non-stale + index-compatible (routing_policy.md notes). JSON/NDJSON no longer bypass a warm index.
- The plain-text native admission is a fail-closed SUBSET, not a flag (perf: skip the
rg subprocess). native_can_serve_plain_text (grep -n "pub const fn native_can_serve_plain_text" rust_core/src/routing.rs — :354 as of this pass) is the single predicate deciding when the in-process native CPU engine may answer a plain-text search instead of spawning rg. Every clause is a refusal: cheap refusals first (plain_text_native_cheap_checks_pass — :369: only PLAIN_TEXT_NATIVE_ALLOWED_FLAGS flags, no structured output, no explicit --format, stdout not a terminal, $RIPGREP_CONFIG_PATH not set-and-non-empty, PATH explicit, exactly one non-empty pattern, exactly one path, that path a regular file and not the - stdin sentinel), then the two expensive clauses evaluated last as a latency contract (pattern is native-renderable; the single path renders identically — a full-content probe: no \r, valid UTF-8, no NUL, ≤ 512 KiB, reported size == bytes read). The admitted route is RoutingDecision::native_cpu_plain_text() (:488) → NativeCpuBackend / routing_reason = plain-text-native with allow_rg_fallback = true, so a native failure still falls back to real rg; in route_search the predicate sits directly in front of the rg arm (!config.native_plain_text is the only thing standing between an admitted request and the rg subprocess — :603-613). Anything outside the subset keeps spawning rg, unchanged. Full clause list + rationale: docs/routing_policy.md §"Admitted plain-text native subset" (:58-75 as of this pass).
- Auto-GPU is conservative and effectively dormant when rg is installed: no fresh positive calibration ⇒ stay CPU-side. GPU CPU-fallback emits
routing_gpu_device_ids = [] and must be called CPU fallback, never GPU acceleration (routing_policy.md §GPU).
- AST routing is a DSL-preference split, not a GPU-capability gate (corrected this pass — the GPU framing below was stale since v1.64.4/#542 and had never been caught).
AstBackend.is_available() (ast_backend.py:505-519) checks ONLY importlib.util.find_spec("tree_sitter") is not None — the earlier torch_geometric/CUDA requirement was dead GNN code (_ast_to_graph), audited as unreachable and deleted in #542; its own docstring now states plainly that gating a working CPU backend behind an unrelated GPU dependency was itself the bug. What actually routes tg run/tg scan to the ast-grep CLI sidecar (AstGrepWrapperBackend) on a typical box is a deliberate DSL-consistency policy, not hardware: the backend-selection block (_select_ast_backend_for_pattern — the REAL implementation now lives in src/tensor_grep/cli/ast_workflows.py (grep -n "^def _select_ast_backend_for_pattern" src/tensor_grep/cli/ast_workflows.py — :1183 as of this pass); main.py's _select_ast_backend_for_pattern (grep -n "^def _select_ast_backend_for_pattern" src/tensor_grep/cli/main.py — :7270) is now a THIN FORWARDING SHIM onto it, whose docstring records that the old hand-maintained main.py duplicate drifted and silently dropped the requires_ast_grep_wrapper fail-closed guard before the collapse. ast_wrapper.is_available() has ZERO occurrences in main.py — the wrapper-availability check is _check_backend_available("AstGrepWrapperBackend") in ast_workflows.py (grep -n "_check_backend_available(\"AstGrepWrapperBackend\")" src/tensor_grep/cli/ast_workflows.py — :1241 as of this pass; helper def :1173). The old main.py pins :6737/:6915 and :6690-6707 are all dead — always grep the symbol in ast_workflows.py) prefers the wrapper whenever it is available, for BOTH pattern kinds, because native tree-sitter AstBackend speaks a different query DSL and would silently return different results if substituted; native AstBackend is reached only as the ast-grep-absent fallback for native-pattern queries (a code comment marks flipping this default as future task #141). Net practical effect on a typical box (ast-grep CLI installed) is unchanged from the old text — tg run still uses the ast-grep CLI sidecar (also for string metavar queries like def $F($$$ARGS)) — visibly, per the fail-closed contract below — but the REASON is DSL-safety, not CUDA-availability; do not repeat the old "requires a CUDA device" claim. This matches code-search-and-retrieval-reference §2 (re-verify that sibling too if it still repeats the old GPU framing).
NativeCpuBackend is not one engine — it is two distinct code paths, and a change proven for one is NOT automatically true for the other (A3, v1.91.3/#695). rust_core/src/native_search.rs is the default streaming path: it is deliberately kept SERIAL, held to a tested ≥25ms first-match latency contract — do not parallelize this path casually; its whole design point is fast first-byte-out, and parallelizing it risks regressing that contract even if aggregate throughput looks better in a microbenchmark. rust_core/src/backend_cpu.rs is the separate PyO3/FFI fallback path (reached only when the search doesn't route through the primary native front door) — this is where #695 shipped intra-file rayon parallel search, gated to files ≥50MiB, byte-identical to the serial result. Before citing a backend_cpu.rs benchmark number as evidence for native_search.rs (or vice versa), confirm which file the change/measurement actually touched — these are two engines behind one routing label, not one engine with two code paths.
The registration sites (miss one → silent misroute)
This is a universal bug class: "register in N places, miss one, fail quietly." The CI registration-completeness gate has been BLOCKING since v1.17.1 / #282 (AGENTS.md, grep -n "registration-completeness gate is BLOCKING" AGENTS.md — :889 as of this pass; the pattern this line used to carry, BLOCKING since v1.17.1, matches NOTHING — the actual sentence is "As of v1.17.1 (#282), the CI registration-completeness gate is BLOCKING", so grep a substring of THAT), but you still author all sites by hand.
Since #977, PR CI is no longer a full routing/parity oracle for docs-only PRs. A cheap changes job (.github/workflows/ci.yml:36) detects whether the PR diff touches code (src/, rust_core/, tests/, .github/workflows/, pyproject.toml, Cargo.toml, Cargo.lock, uv.lock), and the expensive/cross-platform jobs carry needs: [smoke, changes] with if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' (10 jobs as of this pass — re-grep needs: \[smoke, changes\]). A skipped job counts as SUCCESS for branch protection, so a docs-only PR's green rollup proves nothing about code behavior; main pushes always run the full matrix (the job forces CODE_FILES="main-push" off-PR). Do not cite a docs-only PR's CI as routing evidence, and do not propose paths-ignore on required checks (branch protection would wait forever on a run that never starts).
A new top-level tg COMMAND needs four sites (AGENTS.md "Adding a Command or Flag", starting at line 396; re-derive by grepping the header, not the line number, since AGENTS.md's line numbers shift as sections are added above it):
| # |
Site |
File |
| 1 |
KNOWN_COMMANDS set |
src/tensor_grep/cli/commands.py:9 |
| 2 |
Commands::X variant + dispatch arm |
rust_core/src/main.rs:910 (enum, unchanged this pass); e.g. Commands::Prepare/Commands::Ledger dispatch arms — grep -n "Commands::Prepare|Commands::Ledger" rust_core/src/main.rs (was :6691/:6686, now :6966/:6961) |
| 3 |
PUBLIC_TOP_LEVEL_COMMANDS (parity test) |
tests/e2e/test_routing_parity.py:46 |
| 4 |
@app.command function |
src/tensor_grep/cli/main.py |
A new search flag needs two front doors (AGENTS.md, same section, "two front doors") or it leaks to ripgrep and crashes with rg: unrecognized flag for anyone on the published binary:
| # |
Site |
File |
| 1 |
SEARCH_PYTHON_PASSTHROUGH_FLAGS (native allowlist) |
rust_core/src/main.rs:204 |
| 2 |
bootstrap._TG_ONLY_SEARCH_FLAGS (Python front-door allowlist) |
src/tensor_grep/cli/bootstrap.py:50 |
A new MCP tool (or a request/response shape change to an existing one) is a FIFTH registration site — distinct from the four command sites above (AGENTS.md "Adding a Command or Flag", 5th-registration-site note). Every MCP tool's JSON envelope embeds mcp_contract_version from the SINGLE constant _TG_MCP_SERVER_CONTRACT_VERSION (grep -n "_TG_MCP_SERVER_CONTRACT_VERSION = " src/tensor_grep/cli/mcp_server.py — :138 as of this pass, value "1.7.0"); _inject_mcp_contract_fields (grep -n "^def _inject_mcp_contract_fields" src/tensor_grep/cli/mcp_server.py — :1125) HARD-assigns it into every serialized tool envelope (a stale per-tool literal can never win — the M14 retirement of setdefault), and the same constant sets server._mcp_server.version (:185). Bump the constant whenever any tool's request/response shape changes — the tg_find MCP PR (#627) shipped with an un-bumped contract version and only the mandatory adversarial Opus gate caught it, not tests or CI.
Blind spot to internalize: tg callers <fn> finds callable registration sites in ~1s, but the call graph cannot see set/list/decorator registrations — _TG_ONLY_SEARCH_FLAGS is a set, @app.command is a decorator, the Rust dispatch is a match arm. Those are the sites most often missed (--rank lived in a set). So tg callers for the reachable ones and grep / tg scan for the declarative ones, then confirm your entry appears in all sites. (The actual add-a-thing procedure lives in tensor-grep-change-control; this skill only explains why the sites exist.)
Unknown and reserved top-level commands fail closed on BOTH front doors (A90). commands.py carries RESERVED_TOP_LEVEL_COMMANDS (grep -n "RESERVED_TOP_LEVEL_COMMANDS = " src/tensor_grep/cli/commands.py — :77 as of this pass, with the A90 lifecycle comment :67-76): roadmap command names that DO NOT EXIST yet, kept disjoint from KNOWN_COMMANDS (:9) by a test-pinned RESERVED ∩ KNOWN == ∅ invariant — realizing a reserved name means removing it from the reserved set in the same change. A genuinely unknown command is never forwarded to search; both doors refuse it with exit 2, diagnostic on stderr, stdout EMPTY, and a did-you-mean suggestion: the Python bootstrap via _emit_unknown_command_human / _emit_unknown_command_json (grep -n "_emit_unknown_command" src/tensor_grep/cli/bootstrap.py — :444/:454 as of this pass, then raise SystemExit(2) at :1595) and the native binary via top_level_unknown_command_refusal (grep -n "top_level_unknown_command_refusal" rust_core/src/main.rs — refusal block :1347-1373 as of this pass, std::process::exit(2); human text when --help/-h is present, otherwise a single {"error": {"code": "unknown_command", ...}} JSON object on stderr).
Backend Fail-Closed Contract
The single most important correctness invariant. src/tensor_grep/backends/base.py defines it: every ComputeBackend MUST raise BackendExecutionError on a real failure — never return a clean empty / 0-match SearchResult, and never silently swap to an engine that cannot preserve the requested semantics.
Why a context tool cannot afford to violate it: a swallowed backend failure reaches a coding agent as a trustworthy "no matches." That is the one lie a search tool must never tell — the agent then edits on the belief that the symbol does not exist.
Rules when a path can fall back (AGENTS.md "Backend Fail-Closed Contract", backends/base.py:7):
- Fail closed for any flag/contract the fallback cannot preserve.
--pcre2 through a non-PCRE2 engine ⇒ raise, do not swap (that produces wrong results, not just slower ones).
- A legitimate degraded fallback must be VISIBLE: set
fallback_reason (and a distinct routing_reason) on the result so JSON/CLI consumers can tell degraded output from real output. Never label heuristic output as model output.
- Validate an untrusted response shape before indexing (e.g. a model's class count vs a fixed label list) so a mismatch degrades gracefully instead of raising an
IndexError a broad except then swallows.
The recurring anti-pattern: a bare except Exception: that returns empty or falls through to a different engine. This has been fixed repeatedly across audits — the Rust/PCRE2 bridge, the ast-grep OOM mask, the tree-sitter query swallow, CyBERT classify. When you review/write any backend or router that can change engines, this is the first thing to check. The structural fix (a SafeBackendMixin + a fault-injection conformance CI gate) is planned but not yet shipped, so the discipline is still per-file. The same rule extends to routers: an explicit --gpu request silently routed to CPU must raise/emit a diagnostic, not swap silently.
A new command does not inherit a sibling's fail-closed boundary-catch automatically — prove it, don't assume it (tg find, v1.77.0, #189). tg find and tg search --semantic share the same dense-embedding core (retrieval_dense.py/retrieval_fusion.py), but their fail-closed SHAPE differs because their corpora differ: --semantic re-ranks an already regex-prefiltered match set, so a degrade to BM25-only is always cheap and benign; tg find walks and ranks the WHOLE repo with no prefilter, so a query-time model fault reachable mid-walk is a materially different risk surface. The first tg find build wave shipped WITHOUT a command-boundary catch for DenseUnavailableError — it would have propagated as an uncaught crash instead of a visible BM25-degrade — caught only by the mandatory adversarial Opus gate, not by the (green) unit tests, and fixed in the same PR (045fadc). Rule: when a new command reuses an existing backend/compute path, verify its OWN command-boundary exception handling explicitly; do not assume "the underlying module already has a fail-closed contract" is sufficient — the CALLER must also catch and degrade/exit correctly at ITS boundary. See tensor-grep-run-and-operate §11c for tg find's full exit-code contract (BackendExecutionError→exit-2; empty+result_incomplete→exit-2 else exit-1; found+result_incomplete→print then exit-2).
Partial-results contract: suppression != absence (SearchResult.result_incomplete)
Companion invariant to the Backend Fail-Closed Contract above, shipped in round-4 slice 3 (#341, commit f11ce28, v1.18.x). SearchResult (grep -n "^class SearchResult" src/tensor_grep/core/result.py — :71, unchanged this pass; fields — grep -n "result_incomplete: bool\|incomplete_reason: str" src/tensor_grep/core/result.py, was :54-55, now :111-112) carries result_incomplete: bool = False and incomplete_reason: str | None = None, deliberately not overloaded onto fallback_reason — fallback_reason means "the execution engine was swapped"; result_incomplete means "this engine ran, but a soft per-item error suppressed part of the output." Conflating them would emit a false "we fell back" signal to doctor/JSON consumers.
The trigger: rg exit code 2 is a soft per-file error (e.g. one unreadable/missing path among many) and rg still emits matches for every readable file. Before #341, tg's parser raised unconditionally on exit > 1, discarding those partial matches — and even if it hadn't, tg would have silently exited 0 while rg exits 2 (a parity break an agent scripting around exit codes would never see).
And the exit-code side of this contract has since been made STRICTER, not looser — do not describe it as "empty partial -> exit 2, non-empty partial -> exit 0." #398 first made ANY truncated partial exit 2; #399 briefly walked that back to exit-2-only-when-empty; #401 reverted #399 after a unanimous design council — the current, final contract is: any result_incomplete/partial result exits 2 regardless of whether matches were found, because a truncated match/caller/blast-radius list must never be silently trusted as exhaustive. See tensor-grep-large-repo-scale-campaign §5 for the full exit-code table and docs/CONTRACTS.md — grep -n "This mirrors \tg search`'s `2 = result_incomplete`"(was:114, now :157`; CONTRACTS.md grows fast, re-grep before citing) for the symbol-command three-state exit-code contract.
The 5-site fix, cite file:line:
- Parse-first-then-branch —
backends/ripgrep_backend.py (search, _search_files_with_matches, _search_counts): exit 2 with a non-empty parse keeps the results, sets result_incomplete=True + a stderr-derived incomplete_reason (grep -n "result_incomplete = True" src/tensor_grep/backends/ripgrep_backend.py — :144,325,444, essentially unchanged this pass); exit >2, or exit 2 with nothing parsed, raises BackendExecutionError (RESOLVED #79/#10/#14, commit a7c9431: every RipgrepBackend fatal path, including the rg-missing guard — grep -n "requires the 'rg' binary" src/tensor_grep/backends/ripgrep_backend.py (was :505, now :541) — now raises BackendExecutionError instead of a bare RuntimeError, so cli/main.py's per-file except BackendExecutionError CPU-fallback retry — grep -n "except BackendExecutionError" src/tensor_grep/cli/main.py (was :8005, several call sites exist today, e.g. :4796/:8280/:8396; re-grep and confirm which one is the per-file retry before citing a single line) — catches it instead of falling into the broad except Exception and crashing the whole search — see code-search-and-retrieval-reference §1 for the exit-code table).
- Monotonic merge —
merge_runtime_routing (grep -n "^def merge_runtime_routing" src/tensor_grep/core/result.py — was :135, now :142) OR-merges result_incomplete across sub-results (aggregate.result_incomplete or result.result_incomplete), so the CLI/MCP/sidecar aggregate inherits uniformly — any incomplete sub-result taints the whole.
- Exit-code parity —
grep -n "if .*result_incomplete else\|exit_incomplete else" src/tensor_grep/cli/main.py (was a contiguous :8175-8240 block, now scattered across :8549-8620; the wiring moved and split, do not assume a contiguous range): the terminal exits read sys.exit(2 if … result_incomplete/exit_incomplete else …) across the files-with/without-matches, is_empty, quiet, and post-format branches, closing the "tg exits 0 while rg exits 2" gap.
- JSON/NDJSON envelope —
cli/formatters/json_fmt.py (grep -n "result_incomplete" src/tensor_grep/cli/formatters/json_fmt.py; the :126-127 emission pair is unchanged this pass, the second :189-190 cite has drifted — re-grep rather than trust either): result_incomplete/incomplete_reason are emitted only when incomplete, so a complete result's JSON shape stays byte-identical to before #341.
- MCP —
grep -n '"result_incomplete"' src/tensor_grep/cli/mcp_server.py (was 5 representative sites at :2161,4693,4866,4960,5278, all five have since drifted and the symbol now appears at many more line numbers — this file grows fast; re-grep, do not cite a fixed list): the structured tg_search/graph-command responses carry both fields top-level — suppression must be visible to an agent, not buried in a log line.
Rule for any new path that can drop some results due to a soft/partial failure: set result_incomplete + incomplete_reason. Do not (a) raise and lose the good results, or (b) silently return only the good results as if they were the complete answer — that is the same "suppression reads as absence" lie the Backend Fail-Closed Contract forbids, just at the partial-result layer instead of the total-failure layer. Tests: tests/unit/test_rg_exit2_partial.py.
Native-delegation forward-or-refuse contract (_can_delegate_to_native_tg_search)
_can_delegate_to_native_tg_search — grep -n "^def _can_delegate_to_native_tg_search" src/tensor_grep/cli/main.py (was :3709, then :3794, now :4095 as of 2026-08-14) — gates whether a Python-side tg search hands the entire search to the native tg subprocess (_build_native_tg_search_command — grep -n "^def _build_native_tg_search_command" src/tensor_grep/cli/main.py, was :3731, then :3816, now :4117 as of 2026-08-14) and then sys.exit()s on its result — a delegation that runs before the Python-side BM25 rerank (--rank) and the in-backend sort (--sort-files) ever execute.
The invariant: delegation is permitted only when native execution is byte-equivalent to the Python path for the requested config. The gate enforces this mechanically, not by convention — it loops every field name in _NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS (grep -n "_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS = " src/tensor_grep/cli/main.py -- :1966 as of 2026-08-14, was :1894 onward) and refuses delegation (falls through to the Python/backend path) if any of those fields differs from a fresh SearchConfig()'s default. Every SearchConfig field must land in exactly one bucket:
- Forwarded — read by
_build_native_tg_search_command and translated into native argv.
- Refused — listed in
_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS, so a non-default value forces the gate closed.
- Gate-handled — read off explicit keyword args at the call site (
files_with_matches, files_without_match), not the config object.
- KNOWN_GAP — explicitly documented pre-existing tech debt, tracked rather than silently dropped.
This is enforced by a governance ratchet, tests/unit/test_native_delegation_field_coverage.py (round-4 #25, shipped as #342, commit 5e6f780): it AST-derives the "forwarded" set straight from _build_native_tg_search_command's source (ast.walk over every config.<attr> read), so that list can never silently drift from the real code, then asserts all_fields - (forwarded | required | gate_handled | known_gap) == set(). Add a new SearchConfig field and forget to classify it → this test goes red immediately.
The bug this closes (#342): rank_bm25 and sort_files were neither forwarded to native argv nor in the refuse-tuple, so tg search --rank --cpu silently delegated to the native binary — which has no BM25 of its own — and sys.exit()d before the Python rerank/sort ever ran, returning unranked/unsorted output that looked like a normal, correct result (suppression indistinguishable from absence, same class the partial-results contract above targets). This is the same flag-drop bug class as the -u/-uu no-op fixed in #336 (round-4 PR-A slice 1): a flag parses successfully but never reaches the engine that must honor it.
Landmine already hit once — do not re-propose it: the tempting "just gate on any field differing from defaults" fix is wrong. query_pattern is auto-set to the search pattern on every search, so a differs-from-default check would always see a difference and refuse delegation on every call, killing the fast path entirely (the exact failure mode from the 2026-06-30 #1 audit finding — see tensor-grep-failure-archaeology). The fix has to be per-field, not "any field changed."
Rule when adding a new SearchConfig field that affects search output: decide immediately whether native delegation can reproduce it byte-for-byte. If not, add the field name to _NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS. The ratchet test refuses to let you skip this decision silently — it is a hard gate, not a lint suggestion.
A THIRD rg-passthrough door lives INSIDE cli/main.py::search_command — gated on rg availability, not a platform flag (task #24, 2026-07-30)
This is a third, independent rg-passthrough decision, distinct from both the bootstrap front door (bootstrap._run_rg_passthrough, "The front door" above) and the Rust routing.rs tree ("Native-vs-Python routing" above). It lives entirely inside the Python Typer app, fires only for invocations that a _TG_ONLY_SEARCH_FLAGS flag has already forced past the bootstrap front door (e.g. --stats, --ast, --rank, --semantic), and is easy to miss because nothing about it is platform-conditional — yet it produced a real Windows-vs-Linux CI divergence (docs/BACKLOG.md "STILL OPEN — tg search --stats routing DIVERGES BY PLATFORM").
can_passthrough_rg (grep -n "can_passthrough_rg = (" src/tensor_grep/cli/main.py — was :7920-7936, now starts :8020; built by _can_passthrough_rg — grep -n "^def _can_passthrough_rg" src/tensor_grep/cli/main.py, was :5342-5379, now starts :5442) categorically excludes --ast/--rank/--semantic (not config.ast / not config.rank_bm25 / not config.semantic_rank — grep -n "not config.rank_bm25" src/tensor_grep/cli/main.py, was :5359-5363, now :5459-5463) but has no equivalent categorical exclusion for --stats — its only stats-specific veto, not (rg_json_passthrough and stats_mode) (grep -n "rg_json_passthrough and stats_mode" src/tensor_grep/cli/main.py, was :5373, now :5473), fires solely for the --format rg --json combo. A plain-text tg search PAT --stats sails through. This whole area drifted ~100 lines in one release despite being dated 2026-07-30 — re-grep every anchor in this section before citing it, do not trust the numbers below as anything but a drift receipt.
- When
stats=True, search_command has its own branch for it (grep -n "_selected_route_supports_rg_passthrough(" src/tensor_grep/cli/main.py — the call site was :8004-8017, now :8102-8118):if can_passthrough_rg and stats and _selected_route_supports_rg_passthrough(...):
exit_code = rg_backend.search_passthrough(passthrough_paths, pattern, config=config)
sys.exit(exit_code)
This hands the entire search to a live rg subprocess — RipgrepBackend._build_cmd appends rg's own --stats flag when config.stats is set (backends/ripgrep_backend.py:805-806, unchanged this pass) — and exits on rg's own exit code, entirely bypassing everything search_command would otherwise do downstream: its own _emit_stats() [stats] backend=... reason=... line (grep -n "def _emit_stats" src/tensor_grep/cli/main.py — was :8371-8407, now starts :8471), the --debug routing echo (grep -n "routing.backend=" src/tensor_grep/cli/main.py — was :8040-8043, now around :8142-8146), and — the reported symptom — the is_empty branch's defaulted-scope note (grep -n "_write_defaulted_scope_note" src/tensor_grep/cli/main.py — was :8437-8476, now around :8608-8611).
_selected_route_supports_rg_passthrough (grep -n "^def _selected_route_supports_rg_passthrough" src/tensor_grep/cli/main.py — was :5451-5462, now :5551-5562) requires Pipeline.selected_backend_name == "RipgrepBackend", which core/pipeline.py's backend-selection __init__ picks via elif rg_available: self.backend = rg_backend; selected_backend_reason = "rg_default_fast_path" (core/pipeline.py:358-359, essentially unchanged from the cited :356-359) whenever rg_backend.is_available() is True. That in turn is RipgrepBackend.is_available() (backends/ripgrep_backend.py:45, unchanged this pass) → resolve_ripgrep_binary() (grep -n "^def resolve_ripgrep_binary" src/tensor_grep/cli/runtime_paths.py — was :561-591, now starts :576) — a pure environment probe: TG_RG_PATH env var, then shutil.which("rg"/"rg.exe") on PATH, then an in-tree fallback directory that is gitignored (ripgrep-*/ in .gitignore) and absent from a fresh checkout on any platform (only the zip benchmarks/rg.zip is committed, and only a separate test helper — tests/helpers/rg_parity.py::resolve_pinned_rg_binary — knows how to unpack it; production code never touches the zip).
- There is no
sys.platform/os.name conditional anywhere in this chain. The Windows-vs-Linux CI split is caused entirely by whether a real rg/rg.exe happens to be resolvable on PATH inside each OS leg of the test-python CI job (grep -n "^ test-python:" .github/workflows/ci.yml — :360, close to the cited :361-430), which installs no ripgrep package on any OS — contrast the native-build-smoke/smoke/benchmark jobs, which explicitly apt-get install ripgrep / brew install ripgrep (grep -n "install ripgrep" .github/workflows/ci.yml — was :673-685,780-783, now :698-699 (native-build-smoke) and :799 (benchmark-regression)).
- Paired proof (same source tree, one variable flipped, 2026-07-30, Windows): with a real
rg.exe resolvable on PATH, tg search NO_MATCH_ZZZ --stats (no PATH argument) prints rg's own 0 matches / 0 matched lines / ... stats block, no scope note, exit 1. With PATH stripped so resolve_ripgrep_binary() returns None, the identical invocation instead prints tg's own [stats] backend=CPUBackend reason=cpu_python_regex_prefilter line and the is_empty branch's note: no PATH was given..., still exit 1. One boolean (rg_available) is the entire mechanism.
- Why only
--stats XPASSed, not --ast/--rank/--semantic: those three are hard-excluded from _can_passthrough_rg regardless of rg availability (see the exclusions above); --stats is the one flag in that family with no such categorical veto.
- Ruled out: the native Rust
tg binary (resolve_native_tg_binary) is never reachable for --stats on either platform — _can_delegate_to_native_tg_search's unsupported_flags explicitly lists --stats (bootstrap.py:613, unchanged this pass), and the TG_RUST_FIRST_SEARCH OR-branch (grep -n "_prefer_rust_first_search() and not _requires_full_cli" src/tensor_grep/cli/bootstrap.py — was :1525, now :1645) requires not _requires_full_cli(...), which is always False for --stats (_TG_ONLY_SEARCH_FLAGS, bootstrap.py:50, unchanged this pass). cli/mcp_server.py has no can_passthrough_rg/search_passthrough shortcut at all — this divergence is CLI-only.
- Downstream blast radius found while sweeping — CLOSED in this tree; the "still open / zero quiet mentions" text this bullet carried was FALSE. The same
can_passthrough_rg fork also gates the plain, non-stats internal passthrough (grep -n "if can_passthrough_rg:" src/tensor_grep/cli/main.py — was :8037-8042, now :8401), and BOTH passthrough call sites — the plain (if not stats:) branch and the --stats branch, grep -n "search_passthrough" src/tensor_grep/cli/main.py (:8405/:8494 as of this pass) — funnel through the one method that now honors --quiet: RipgrepBackend.search_passthrough (grep -n "def search_passthrough" src/tensor_grep/backends/ripgrep_backend.py — :478) appends rg's -q at :511 when config.quiet is set. It is deliberately NOT inside the shared _build_cmd (:531): _build_cmd has FOUR consumers — search() (:80, parses --json), _search_files_with_matches() (:287, parses -l), _search_counts() (:406, parses --count) — and only search_passthrough streams rg's output; -q makes rg print NOTHING, so a parsing consumer would report a false zero-match on a matching file plus an exit-code violation (the in-code hazard comment at :492-510 records the measured rg behavior). Placement is pinned by tests/unit/test_quiet_survives_rg_passthrough.py: the fix arm asserts -q reaches the streaming route's argv, the control arms assert the three parsing consumers never receive -q and that a non-quiet search is never silenced. Do not re-propose moving -q into _build_cmd. (Honesty note: no test combines --stats AND --quiet specifically; coverage is at the search_passthrough choke point both branches share.)
- Confirm on CI in one line, run inside the
test-python job matrix on each OS (no test framework needed):python -c "from tensor_grep.cli.runtime_paths import resolve_ripgrep_binary as r; print(r())"
A real path on one OS and None on the other reproduces the whole divergence directly.
The walk-ceiling fast-refuse: 3 doors, 2 constants, 1 value (A9, v1.92.3/#702)
Before #702, the plain flag-less bootstrap._run_rg_passthrough path (grep -n "^def _run_rg_passthrough" src/tensor_grep/cli/bootstrap.py — was :1088, now :1421 — the front
door a bare tg search PATTERN with no scoping flags hits, before main.py's Typer app is ever
reached) had no walk ceiling at all. main.py's three vendored/workspace/large-root refusal guards
never ran for this path, so an unscoped search on a large defaulted-path root silently walked unbounded
until it hit the 60s TG_RG_TIMEOUT_SECONDS subprocess backstop — natively reproduced, not a WSL
filesystem artifact.
The fix is one constant, enforced coherently across 3 doors, not three independent numbers that can
drift apart:
…(truncated)
1---2name: tensor-grep-architecture-contract3description: Use when you need the load-bearing design of tensor-grep and WHY it holds before touching cli/bootstrap.py, rust_core/src/main.rs, backends/, core/result.py, cli/main.py's native-delegation gate, routing, the agent capsule, or before reviewing/planning any change to the front door, command/flag registration, or backend contract. Explains the bootstrap intercept-before-Typer front door, native-vs-Python routing, the 4 command + 2 flag registration sites plus the MCP contract-version site, the Backend Fail-Closed Contract, the native-delegation forward-or-refuse contract (`_can_delegate_to_native_tg_search` + its field-coverage ratchet), the partial-results `result_incomplete`/`incomplete_reason` envelope, `MatchLine`'s frozen-but-hashable dataclass contract, the ASCII-only CLI output rule, the agent-context moat, the invariants that must hold, and the known-weak points (flat no-IDF scorer, GPU not viable, rg parity gap, FFI not the dir-scan speed path). Read this to build the right mental model; use sibling sk4---56# tensor-grep architecture contract78**What this is.** A ground-truthed map of tensor-grep's load-bearing design: the invariants a change must not break, and the weak points you must not oversell. Read it to understand *why* the code is shaped this way before you touch it. It is not a how-to — for that, hand off to a sibling (routing table below).910**What tensor-grep is** (as of 2026-07-24, v1.95.0, `pyproject.toml`): a code-intelligence CLI named `tg`. A Rust core (`rust_core/` — both a PyO3 extension *and* a standalone `tg` binary) plus a Python CLI (`src/tensor_grep/`). Apache-2.0. Ships to PyPI (package `tensor-grep`), npm, Homebrew, winget. CONTRIBUTING.md calls it a "benchmark-governed, contract-heavy codebase" — that is the whole point: the contracts below are enforced by tests and a CI gate, not by convention.1112## When to use this skill vs a sibling1314| You are about to… | Use |15|---|---|16| Understand *why* the front door / routing / backend contract exists (this skill) | **you are here** |17| Add/rename a command or a search flag; ship a change safely | `tensor-grep-change-control` |18| Debug a live misroute, hang, wrong-result, or "no matches that should match" | `tensor-grep-debugging-playbook` |19| Study a settled past failure so you don't re-fight it | `tensor-grep-failure-archaeology` |20| Use `tg` as a *user* (search/orient/callers/agent flags) | `code-search-and-retrieval-reference`, or the `.claude/skills/tensor-grep/` usage skill |21| Set/override config or env axes | `tensor-grep-config-and-flags` |22| Build the Rust ext / set up the toolchain | `tensor-grep-build-and-env` |23| Run diagnostics (`doctor`, `dogfood`, readiness) | `tensor-grep-diagnostics-and-tooling` |24| Make or defend a speed/quality claim with numbers | `tensor-grep-benchmark-and-proof-toolkit` |25| Position the product / write release notes | `tensor-grep-release-and-positioning` |2627**Do not use this skill to authorize a change.** It explains the design; it does not route around `tensor-grep-change-control` or the project's PR/council/dogfood discipline. Any code change still goes through change-control.2829## Jargon (defined once)3031- **Front door / bootstrap** — the process entry point `tensor_grep.cli.bootstrap:main_entry` that sees raw `argv` *before* the Typer app.32- **Typer app** — the Python click/Typer CLI in `src/tensor_grep/cli/main.py` (`@app.command` functions). It is the *inner* CLI, not the front door.33- **Native binary / native front door** — the standalone Rust `tg` binary built from `rust_core/`. Fast path for search routing.34- **Sidecar** — Python doing work the native binary bounces to it (`TG_SIDECAR_PYTHON`).35- **CliRunner** — Typer's in-process test harness. It calls the Typer app directly and **bypasses the bootstrap front door** — the single most important test-coverage caveat in this repo.36- **rg** — ripgrep. **ast-grep** — structural (AST) search. Both are baselines tg is measured against, not beaten.37- **Capsule** — the Actionable Context Capsule emitted by `tg agent` (`capsule_version = 1`).3839## The front door: intercept before Typer4041`tg` is not "a Typer app." The published entry point is `bootstrap.main_entry` — `grep -n "^def main_entry" src/tensor_grep/cli/bootstrap.py` (was `:1444`, now `:1550`; this anchor has drifted three times, never cite the number as fact). It parses `argv` itself and, for a **plain text search**, forwards to the native `tg` binary or to ripgrep *before Typer ever runs* (re-grep `_run_rg_passthrough` — the dispatch runs from the `_normalize_search_invocation` call through the final `raise SystemExit(_run_rg_passthrough(...))`; traced live 2026-07-29 and confirmed a bare `tg search PAT` exits THERE, never reaching Typer). The Typer app is only reached for TG-only flags, help, or commands that require full CLI (`grep -n "^def _requires_full_cli" src/tensor_grep/cli/bootstrap.py` — `:480`, unchanged this pass).4243Why this matters, concretely:4445- **CliRunner cannot see routing bugs.** It invokes the Typer app directly, so any bug in `bootstrap` routing (a flag that leaks to `rg`, a fork-bomb delegation loop, a wrong native/Python choice) is **invisible** to CliRunner tests and green in CI while broken for real users. This is exactly how the `--rank` plain-text crash shipped (`AGENTS.md` §"Dogfood the Real Binary, Not CliRunner" — `grep -n "^## Dogfood the Real Binary"` — was `:422`, now `:957`). **Rule: verify front-door behavior against the REAL published binary** via `scripts/dogfood/` (Dockerfile + `dogfood_features.py`), never CliRunner alone.46- **Two mutual-delegation fork-bomb hazards are guarded, not theoretical.** `TG_REEXEC_GUARD` (`grep -n 'os.environ.get("TG_REEXEC_GUARD")' src/tensor_grep/cli/bootstrap.py` — was `:1497`, now `:1617`) stops native→python→native search loops; `_json_aggregate_blocks_passthrough` (`grep -n "^def _json_aggregate_blocks_passthrough" src/tensor_grep/cli/bootstrap.py` — `:572`, unchanged this pass) stops `--json` + a render-only flag (e.g. `-b`) from deadlocking the native front door; `_run_requires_ast_workflow` (`grep -n "^def _run_requires_ast_workflow" src/tensor_grep/cli/bootstrap.py` — was `:1385`, now `:1491`) keeps `tg run --selector/--strictness/--stdin/--globs` in Python so it does not ping-pong. If you touch delegation, you can re-arm a fork bomb — see `tensor-grep-failure-archaeology`.4748## Native-vs-Python routing (the decision tree)4950Search routing is a single shared decision in `rust_core/src/routing.rs::route_search(...)` (documented in `docs/routing_policy.md`). It returns a `RoutingDecision` carrying `selection`, `routing_backend`, `routing_reason`, `sidecar_used`, `allow_rg_fallback`. Priority order (routing_policy.md §"Unified `tg search` decision tree"):51521. `--index` → `TrigramIndex` (highest override)532. `--gpu-device-ids` → `NativeGpuBackend` (overrides warm-index + size routing; **must fail loud if unhonorable**)543. `--force-cpu`/`--cpu` with structured output or no usable `rg` → `NativeCpuBackend`554. AST command → `AstBackend`565. Warm non-stale compatible `.tg_index` → `TrigramIndex`576. corpus > calibrated threshold **and** GPU available **and** calibration positive → `NativeGpuBackend`587. else, plain-text request ADMITTED by `native_can_serve_plain_text` → `NativeCpuBackend` (`routing_reason = plain-text-native`; skips the `rg` spawn entirely — see the clause bullet below)598. else, `rg` available and no structured output → `RipgrepBackend`609. else → `NativeCpuBackend`6110. native CPU route fails and `allow_rg_fallback` → `RipgrepBackend` final fallback6263Load-bearing consequences:6465- **`rg` is the normal cold-path backend when installed.** Native CPU is the default *only* for structured output (`--json`/`--ndjson`), explicit `--cpu`, warm index, AST, and GPU fallback. Do not "optimize" tg to beat rg on cold text — that is the parity tier (see Known-Weak §3).66- **Warm-index auto-routing is gated:** pattern ≥ 3 bytes, no `-v`, `-C`, `--max-count`, `-w`, `-g`, and the cache must exist + be non-stale + index-compatible (routing_policy.md notes). JSON/NDJSON no longer bypass a warm index.67- **The plain-text native admission is a fail-closed SUBSET, not a flag (perf: skip the `rg` subprocess).** `native_can_serve_plain_text` (`grep -n "pub const fn native_can_serve_plain_text" rust_core/src/routing.rs` — `:354` as of this pass) is the single predicate deciding when the in-process native CPU engine may answer a plain-text search instead of spawning `rg`. Every clause is a refusal: cheap refusals first (`plain_text_native_cheap_checks_pass` — `:369`: only `PLAIN_TEXT_NATIVE_ALLOWED_FLAGS` flags, no structured output, no explicit `--format`, stdout not a terminal, `$RIPGREP_CONFIG_PATH` not set-and-non-empty, PATH explicit, exactly one non-empty pattern, exactly one path, that path a regular file and not the `-` stdin sentinel), then the two expensive clauses evaluated last as a latency contract (pattern is native-renderable; the single path renders identically — a full-content probe: no `\r`, valid UTF-8, no NUL, ≤ 512 KiB, reported size == bytes read). The admitted route is `RoutingDecision::native_cpu_plain_text()` (`:488`) → `NativeCpuBackend` / `routing_reason = plain-text-native` with `allow_rg_fallback = true`, so a native failure still falls back to real `rg`; in `route_search` the predicate sits directly in front of the `rg` arm (`!config.native_plain_text` is the only thing standing between an admitted request and the `rg` subprocess — `:603-613`). Anything outside the subset keeps spawning `rg`, unchanged. Full clause list + rationale: `docs/routing_policy.md` §"Admitted plain-text native subset" (`:58-75` as of this pass).68- **Auto-GPU is conservative and effectively dormant** when rg is installed: no fresh positive calibration ⇒ stay CPU-side. GPU CPU-fallback emits `routing_gpu_device_ids = []` and must be called *CPU fallback*, never GPU acceleration (routing_policy.md §GPU).69- **AST routing is a DSL-preference split, not a GPU-capability gate (corrected this pass — the GPU framing below was stale since v1.64.4/#542 and had never been caught).** `AstBackend.is_available()` (`ast_backend.py:505-519`) checks ONLY `importlib.util.find_spec("tree_sitter") is not None` — the earlier `torch_geometric`/CUDA requirement was dead GNN code (`_ast_to_graph`), audited as unreachable and deleted in #542; its own docstring now states plainly that gating a working CPU backend behind an unrelated GPU dependency was itself the bug. What actually routes `tg run`/`tg scan` to the `ast-grep` CLI sidecar (`AstGrepWrapperBackend`) on a typical box is a **deliberate DSL-consistency policy**, not hardware: the backend-selection block (`_select_ast_backend_for_pattern` — the REAL implementation now lives in `src/tensor_grep/cli/ast_workflows.py` (`grep -n "^def _select_ast_backend_for_pattern" src/tensor_grep/cli/ast_workflows.py` — `:1183` as of this pass); `main.py`'s `_select_ast_backend_for_pattern` (`grep -n "^def _select_ast_backend_for_pattern" src/tensor_grep/cli/main.py` — `:7270`) is now a THIN FORWARDING SHIM onto it, whose docstring records that the old hand-maintained main.py duplicate drifted and silently dropped the `requires_ast_grep_wrapper` fail-closed guard before the collapse. `ast_wrapper.is_available()` has ZERO occurrences in `main.py` — the wrapper-availability check is `_check_backend_available("AstGrepWrapperBackend")` in `ast_workflows.py` (`grep -n "_check_backend_available(\"AstGrepWrapperBackend\")" src/tensor_grep/cli/ast_workflows.py` — `:1241` as of this pass; helper def `:1173`). The old main.py pins `:6737`/`:6915` and `:6690-6707` are all dead — always grep the symbol in `ast_workflows.py`) prefers the wrapper whenever it is available, for BOTH pattern kinds, because native tree-sitter `AstBackend` speaks a different query DSL and would silently return different results if substituted; native `AstBackend` is reached only as the ast-grep-absent fallback for native-pattern queries (a code comment marks flipping this default as future task #141). Net practical effect on a typical box (ast-grep CLI installed) is unchanged from the old text — `tg run` still uses the ast-grep CLI sidecar (also for string metavar queries like `def $F($$$ARGS)`) — visibly, per the fail-closed contract below — but the REASON is DSL-safety, not CUDA-availability; do not repeat the old "requires a CUDA device" claim. This matches `code-search-and-retrieval-reference` §2 (re-verify that sibling too if it still repeats the old GPU framing).70- **`NativeCpuBackend` is not one engine — it is two distinct code paths, and a change proven for one is NOT automatically true for the other (A3, v1.91.3/#695).** `rust_core/src/native_search.rs` is the **default streaming** path: it is deliberately kept SERIAL, held to a tested **≥25ms first-match latency contract** — do not parallelize this path casually; its whole design point is fast first-byte-out, and parallelizing it risks regressing that contract even if aggregate throughput looks better in a microbenchmark. `rust_core/src/backend_cpu.rs` is the separate **PyO3/FFI fallback path** (reached only when the search doesn't route through the primary native front door) — this is where #695 shipped intra-file `rayon` parallel search, gated to files **≥50MiB**, byte-identical to the serial result. Before citing a `backend_cpu.rs` benchmark number as evidence for `native_search.rs` (or vice versa), confirm which file the change/measurement actually touched — these are two engines behind one routing label, not one engine with two code paths.7172## The registration sites (miss one → silent misroute)7374This is a **universal bug class**: "register in N places, miss one, fail *quietly*." The CI registration-completeness gate has been **BLOCKING since v1.17.1 / #282** (`AGENTS.md`, `grep -n "registration-completeness gate is BLOCKING" AGENTS.md` — `:889` as of this pass; **the pattern this line used to carry, `BLOCKING since v1.17.1`, matches NOTHING** — the actual sentence is "As of v1.17.1 (#282), the CI registration-completeness gate is BLOCKING", so grep a substring of THAT), but you still author all sites by hand.7576**Since #977, PR CI is no longer a full routing/parity oracle for docs-only PRs.** A cheap `changes` job (`.github/workflows/ci.yml:36`) detects whether the PR diff touches code (`src/`, `rust_core/`, `tests/`, `.github/workflows/`, `pyproject.toml`, `Cargo.toml`, `Cargo.lock`, `uv.lock`), and the expensive/cross-platform jobs carry `needs: [smoke, changes]` with `if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true'` (10 jobs as of this pass — re-grep `needs: \[smoke, changes\]`). A skipped job counts as SUCCESS for branch protection, so a docs-only PR's green rollup proves nothing about code behavior; main pushes always run the full matrix (the job forces `CODE_FILES="main-push"` off-PR). Do not cite a docs-only PR's CI as routing evidence, and do not propose `paths-ignore` on required checks (branch protection would wait forever on a run that never starts).7778**A new top-level `tg COMMAND` needs four sites** (AGENTS.md "Adding a Command or Flag", starting at line 396; re-derive by grepping the header, not the line number, since AGENTS.md's line numbers shift as sections are added above it):7980| # | Site | File |81|---|---|---|82| 1 | `KNOWN_COMMANDS` set | `src/tensor_grep/cli/commands.py:9` |83| 2 | `Commands::X` variant + dispatch arm | `rust_core/src/main.rs:910` (enum, unchanged this pass); e.g. `Commands::Prepare`/`Commands::Ledger` dispatch arms — `grep -n "Commands::Prepare\|Commands::Ledger" rust_core/src/main.rs` (was `:6691`/`:6686`, now `:6966`/`:6961`) |84| 3 | `PUBLIC_TOP_LEVEL_COMMANDS` (parity test) | `tests/e2e/test_routing_parity.py:46` |85| 4 | `@app.command` function | `src/tensor_grep/cli/main.py` |8687**A new search flag needs two front doors** (AGENTS.md, same section, "two front doors") or it leaks to ripgrep and crashes with `rg: unrecognized flag` for anyone on the published binary:8889| # | Site | File |90|---|---|---|91| 1 | `SEARCH_PYTHON_PASSTHROUGH_FLAGS` (native allowlist) | `rust_core/src/main.rs:204` |92| 2 | `bootstrap._TG_ONLY_SEARCH_FLAGS` (Python front-door allowlist) | `src/tensor_grep/cli/bootstrap.py:50` |9394**A new MCP tool (or a request/response shape change to an existing one) is a FIFTH registration site** — distinct from the four command sites above (AGENTS.md "Adding a Command or Flag", 5th-registration-site note). Every MCP tool's JSON envelope embeds `mcp_contract_version` from the SINGLE constant `_TG_MCP_SERVER_CONTRACT_VERSION` (`grep -n "_TG_MCP_SERVER_CONTRACT_VERSION = " src/tensor_grep/cli/mcp_server.py` — `:138` as of this pass, value `"1.7.0"`); `_inject_mcp_contract_fields` (`grep -n "^def _inject_mcp_contract_fields" src/tensor_grep/cli/mcp_server.py` — `:1125`) HARD-assigns it into every serialized tool envelope (a stale per-tool literal can never win — the M14 retirement of `setdefault`), and the same constant sets `server._mcp_server.version` (`:185`). **Bump the constant whenever any tool's request/response shape changes** — the `tg_find` MCP PR (#627) shipped with an un-bumped contract version and only the mandatory adversarial Opus gate caught it, not tests or CI.9596**Blind spot to internalize:** `tg callers <fn>` finds *callable* registration sites in ~1s, but the call graph **cannot see set/list/decorator registrations** — `_TG_ONLY_SEARCH_FLAGS` is a set, `@app.command` is a decorator, the Rust dispatch is a match arm. Those are the sites most often missed (`--rank` lived in a *set*). So `tg callers` for the reachable ones **and** grep / `tg scan` for the declarative ones, then confirm your entry appears in *all* sites. (The actual add-a-thing procedure lives in `tensor-grep-change-control`; this skill only explains why the sites exist.)9798**Unknown and reserved top-level commands fail closed on BOTH front doors (A90).** `commands.py` carries `RESERVED_TOP_LEVEL_COMMANDS` (`grep -n "RESERVED_TOP_LEVEL_COMMANDS = " src/tensor_grep/cli/commands.py` — `:77` as of this pass, with the A90 lifecycle comment `:67-76`): roadmap command names that DO NOT EXIST yet, kept disjoint from `KNOWN_COMMANDS` (`:9`) by a test-pinned `RESERVED ∩ KNOWN == ∅` invariant — realizing a reserved name means removing it from the reserved set in the same change. A genuinely unknown command is never forwarded to search; both doors refuse it with **exit 2, diagnostic on stderr, stdout EMPTY, and a did-you-mean suggestion**: the Python bootstrap via `_emit_unknown_command_human` / `_emit_unknown_command_json` (`grep -n "_emit_unknown_command" src/tensor_grep/cli/bootstrap.py` — `:444`/`:454` as of this pass, then `raise SystemExit(2)` at `:1595`) and the native binary via `top_level_unknown_command_refusal` (`grep -n "top_level_unknown_command_refusal" rust_core/src/main.rs` — refusal block `:1347-1373` as of this pass, `std::process::exit(2)`; human text when `--help`/`-h` is present, otherwise a single `{"error": {"code": "unknown_command", ...}}` JSON object on stderr).99100## Backend Fail-Closed Contract101102The single most important correctness invariant. `src/tensor_grep/backends/base.py` defines it: every `ComputeBackend` **MUST raise `BackendExecutionError` on a real failure** — never return a clean empty / `0-match` `SearchResult`, and never silently swap to an engine that cannot preserve the requested semantics.103104Why a context tool cannot afford to violate it: a swallowed backend failure reaches a coding agent as a trustworthy "no matches." That is the one lie a search tool must never tell — the agent then edits on the belief that the symbol does not exist.105106Rules when a path *can* fall back (AGENTS.md "Backend Fail-Closed Contract", `backends/base.py:7`):107108- **Fail closed** for any flag/contract the fallback cannot preserve. `--pcre2` through a non-PCRE2 engine ⇒ raise, do not swap (that produces *wrong results*, not just slower ones).109- **A legitimate degraded fallback must be VISIBLE:** set `fallback_reason` (and a distinct `routing_reason`) on the result so JSON/CLI consumers can tell degraded output from real output. Never label heuristic output as model output.110- **Validate an untrusted response shape before indexing** (e.g. a model's class count vs a fixed label list) so a mismatch degrades gracefully instead of raising an `IndexError` a broad `except` then swallows.111112**The recurring anti-pattern:** a bare `except Exception:` that returns empty or falls through to a different engine. This has been fixed *repeatedly* across audits — the Rust/PCRE2 bridge, the ast-grep OOM mask, the tree-sitter query swallow, CyBERT classify. When you review/write any backend or router that can change engines, this is the first thing to check. The structural fix (a `SafeBackendMixin` + a fault-injection conformance CI gate) is planned but **not yet shipped**, so the discipline is still per-file. The same rule extends to routers: an explicit `--gpu` request silently routed to CPU must raise/emit a diagnostic, not swap silently.113114**A new command does not inherit a sibling's fail-closed boundary-catch automatically — prove it, don't assume it (`tg find`, v1.77.0, #189).** `tg find` and `tg search --semantic` share the same dense-embedding core (`retrieval_dense.py`/`retrieval_fusion.py`), but their fail-closed SHAPE differs because their corpora differ: `--semantic` re-ranks an already regex-prefiltered match set, so a degrade to BM25-only is always cheap and benign; `tg find` walks and ranks the WHOLE repo with no prefilter, so a query-time model fault reachable mid-walk is a materially different risk surface. The first `tg find` build wave shipped WITHOUT a command-boundary catch for `DenseUnavailableError` — it would have propagated as an uncaught crash instead of a visible BM25-degrade — caught only by the mandatory adversarial Opus gate, not by the (green) unit tests, and fixed in the same PR (`045fadc`). **Rule:** when a new command reuses an existing backend/compute path, verify its OWN command-boundary exception handling explicitly; do not assume "the underlying module already has a fail-closed contract" is sufficient — the CALLER must also catch and degrade/exit correctly at ITS boundary. See `tensor-grep-run-and-operate` §11c for `tg find`'s full exit-code contract (`BackendExecutionError`→exit-2; empty+`result_incomplete`→exit-2 else exit-1; found+`result_incomplete`→print then exit-2).115116## Partial-results contract: suppression != absence (`SearchResult.result_incomplete`)117118Companion invariant to the Backend Fail-Closed Contract above, shipped in round-4 slice 3 (#341, commit `f11ce28`, v1.18.x). `SearchResult` (`grep -n "^class SearchResult" src/tensor_grep/core/result.py` — `:71`, unchanged this pass; fields — `grep -n "result_incomplete: bool\|incomplete_reason: str" src/tensor_grep/core/result.py`, was `:54-55`, now `:111-112`) carries `result_incomplete: bool = False` and `incomplete_reason: str | None = None`, deliberately **not** overloaded onto `fallback_reason` — `fallback_reason` means "the execution engine was swapped"; `result_incomplete` means "this engine ran, but a soft per-item error suppressed part of the output." Conflating them would emit a false "we fell back" signal to `doctor`/JSON consumers.119120The trigger: rg exit code **2** is a *soft* per-file error (e.g. one unreadable/missing path among many) and rg still emits matches for every readable file. Before #341, tg's parser raised unconditionally on `exit > 1`, **discarding those partial matches** — and even if it hadn't, tg would have silently exited 0 while rg exits 2 (a parity break an agent scripting around exit codes would never see).121122**And the exit-code side of this contract has since been made STRICTER, not looser — do not describe it as "empty partial -> exit 2, non-empty partial -> exit 0."** #398 first made ANY truncated partial exit 2; #399 briefly walked that back to exit-2-only-when-empty; **#401 reverted #399** after a unanimous design council — the current, final contract is: any `result_incomplete`/`partial` result exits **2 regardless of whether matches were found**, because a truncated match/caller/blast-radius list must never be silently trusted as exhaustive. See `tensor-grep-large-repo-scale-campaign` §5 for the full exit-code table and `docs/CONTRACTS.md` — `grep -n "This mirrors \`tg search\`'s \`2 = result_incomplete\`"` (was `:114`, now `:157`; CONTRACTS.md grows fast, re-grep before citing) for the symbol-command three-state exit-code contract.123124The 5-site fix, cite `file:line`:125126- **Parse-first-then-branch** — `backends/ripgrep_backend.py` (`search`, `_search_files_with_matches`, `_search_counts`): exit 2 with a non-empty parse *keeps* the results, sets `result_incomplete=True` + a stderr-derived `incomplete_reason` (`grep -n "result_incomplete = True" src/tensor_grep/backends/ripgrep_backend.py` — `:144,325,444`, essentially unchanged this pass); exit >2, or exit 2 with nothing parsed, raises `BackendExecutionError` (**RESOLVED #79/#10/#14, commit `a7c9431`**: every `RipgrepBackend` fatal path, including the rg-missing guard — `grep -n "requires the 'rg' binary" src/tensor_grep/backends/ripgrep_backend.py` (was `:505`, now `:541`) — now raises `BackendExecutionError` instead of a bare `RuntimeError`, so `cli/main.py`'s per-file `except BackendExecutionError` CPU-fallback retry — `grep -n "except BackendExecutionError" src/tensor_grep/cli/main.py` (was `:8005`, several call sites exist today, e.g. `:4796`/`:8280`/`:8396`; re-grep and confirm which one is the per-file retry before citing a single line) — catches it instead of falling into the broad `except Exception` and crashing the whole search — see `code-search-and-retrieval-reference` §1 for the exit-code table).127- **Monotonic merge** — `merge_runtime_routing` (`grep -n "^def merge_runtime_routing" src/tensor_grep/core/result.py` — was `:135`, now `:142`) OR-merges `result_incomplete` across sub-results (`aggregate.result_incomplete or result.result_incomplete`), so the CLI/MCP/sidecar aggregate inherits uniformly — any incomplete sub-result taints the whole.128- **Exit-code parity** — `grep -n "if .*result_incomplete else\|exit_incomplete else" src/tensor_grep/cli/main.py` (was a contiguous `:8175-8240` block, now scattered across `:8549-8620`; the wiring moved and split, do not assume a contiguous range): the terminal exits read `sys.exit(2 if … result_incomplete/exit_incomplete else …)` across the files-with/without-matches, `is_empty`, quiet, and post-format branches, closing the "tg exits 0 while rg exits 2" gap.129- **JSON/NDJSON envelope** — `cli/formatters/json_fmt.py` (`grep -n "result_incomplete" src/tensor_grep/cli/formatters/json_fmt.py`; the `:126-127` emission pair is unchanged this pass, the second `:189-190` cite has drifted — re-grep rather than trust either): `result_incomplete`/`incomplete_reason` are emitted **only when incomplete**, so a complete result's JSON shape stays byte-identical to before #341.130- **MCP** — `grep -n '"result_incomplete"' src/tensor_grep/cli/mcp_server.py` (was 5 representative sites at `:2161,4693,4866,4960,5278`, all five have since drifted and the symbol now appears at many more line numbers — this file grows fast; re-grep, do not cite a fixed list): the structured `tg_search`/graph-command responses carry both fields top-level — suppression must be visible to an agent, not buried in a log line.131132**Rule for any new path that can drop some results due to a soft/partial failure:** set `result_incomplete` + `incomplete_reason`. Do not (a) raise and lose the good results, or (b) silently return only the good results as if they were the complete answer — that is the same "suppression reads as absence" lie the Backend Fail-Closed Contract forbids, just at the partial-result layer instead of the total-failure layer. Tests: `tests/unit/test_rg_exit2_partial.py`.133134## Native-delegation forward-or-refuse contract (`_can_delegate_to_native_tg_search`)135136`_can_delegate_to_native_tg_search` — `grep -n "^def _can_delegate_to_native_tg_search" src/tensor_grep/cli/main.py` (was `:3709`, then `:3794`, now `:4095` as of 2026-08-14) — gates whether a Python-side `tg search` hands the **entire** search to the native `tg` subprocess (`_build_native_tg_search_command` — `grep -n "^def _build_native_tg_search_command" src/tensor_grep/cli/main.py`, was `:3731`, then `:3816`, now `:4117` as of 2026-08-14) and then `sys.exit()`s on its result — a delegation that runs *before* the Python-side BM25 rerank (`--rank`) and the in-backend sort (`--sort-files`) ever execute.137138**The invariant:** delegation is permitted only when native execution is byte-equivalent to the Python path for the requested config. The gate enforces this mechanically, not by convention — it loops every field name in `_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS` (`grep -n "_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS = " src/tensor_grep/cli/main.py` -- `:1966` as of 2026-08-14, was `:1894` onward) and **refuses** delegation (falls through to the Python/backend path) if *any* of those fields differs from a fresh `SearchConfig()`'s default. Every `SearchConfig` field must land in exactly one bucket:1391401. **Forwarded** — read by `_build_native_tg_search_command` and translated into native argv.1412. **Refused** — listed in `_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS`, so a non-default value forces the gate closed.1423. **Gate-handled** — read off explicit keyword args at the call site (`files_with_matches`, `files_without_match`), not the config object.1434. **KNOWN_GAP** — explicitly documented pre-existing tech debt, tracked rather than silently dropped.144145This is enforced by a governance **ratchet**, `tests/unit/test_native_delegation_field_coverage.py` (round-4 #25, shipped as #342, commit `5e6f780`): it AST-derives the "forwarded" set straight from `_build_native_tg_search_command`'s source (`ast.walk` over every `config.<attr>` read), so that list can never silently drift from the real code, then asserts `all_fields - (forwarded | required | gate_handled | known_gap) == set()`. Add a new `SearchConfig` field and forget to classify it → this test goes red immediately.146147**The bug this closes (#342):** `rank_bm25` and `sort_files` were neither forwarded to native argv nor in the refuse-tuple, so `tg search --rank --cpu` silently delegated to the native binary — which has no BM25 of its own — and `sys.exit()`d *before* the Python rerank/sort ever ran, returning unranked/unsorted output that looked like a normal, correct result (suppression indistinguishable from absence, same class the partial-results contract above targets). This is the **same flag-drop bug class** as the `-u`/`-uu` no-op fixed in #336 (round-4 PR-A slice 1): a flag parses successfully but never reaches the engine that must honor it.148149**Landmine already hit once — do not re-propose it:** the tempting "just gate on any field differing from defaults" fix is wrong. `query_pattern` is auto-set to the search pattern on *every* search, so a differs-from-default check would always see a difference and refuse delegation on every call, killing the fast path entirely (the exact failure mode from the 2026-06-30 #1 audit finding — see `tensor-grep-failure-archaeology`). The fix has to be per-field, not "any field changed."150151**Rule when adding a new `SearchConfig` field that affects search output:** decide immediately whether native delegation can reproduce it byte-for-byte. If not, add the field name to `_NATIVE_TG_DELEGATION_DEFAULT_REQUIRED_FIELDS`. The ratchet test refuses to let you skip this decision silently — it is a hard gate, not a lint suggestion.152153## A THIRD rg-passthrough door lives INSIDE `cli/main.py::search_command` — gated on `rg` availability, not a platform flag (task #24, 2026-07-30)154155This is a **third**, independent rg-passthrough decision, distinct from both the bootstrap front door (`bootstrap._run_rg_passthrough`, "The front door" above) and the Rust `routing.rs` tree ("Native-vs-Python routing" above). It lives entirely inside the Python Typer app, fires only for invocations that a `_TG_ONLY_SEARCH_FLAGS` flag has already forced past the bootstrap front door (e.g. `--stats`, `--ast`, `--rank`, `--semantic`), and is easy to miss because nothing about it is platform-conditional — yet it produced a real Windows-vs-Linux CI divergence (`docs/BACKLOG.md` "STILL OPEN — `tg search --stats` routing DIVERGES BY PLATFORM").156157- `can_passthrough_rg` (`grep -n "can_passthrough_rg = (" src/tensor_grep/cli/main.py` — was `:7920-7936`, now starts `:8020`; built by `_can_passthrough_rg` — `grep -n "^def _can_passthrough_rg" src/tensor_grep/cli/main.py`, was `:5342-5379`, now starts `:5442`) categorically excludes `--ast`/`--rank`/`--semantic` (`not config.ast` / `not config.rank_bm25` / `not config.semantic_rank` — `grep -n "not config.rank_bm25" src/tensor_grep/cli/main.py`, was `:5359-5363`, now `:5459-5463`) but has **no equivalent categorical exclusion for `--stats`** — its only stats-specific veto, `not (rg_json_passthrough and stats_mode)` (`grep -n "rg_json_passthrough and stats_mode" src/tensor_grep/cli/main.py`, was `:5373`, now `:5473`), fires solely for the `--format rg --json` combo. A plain-text `tg search PAT --stats` sails through. **This whole area drifted ~100 lines in one release despite being dated 2026-07-30 — re-grep every anchor in this section before citing it, do not trust the numbers below as anything but a drift receipt.**158- When `stats=True`, `search_command` has its own branch for it (`grep -n "_selected_route_supports_rg_passthrough(" src/tensor_grep/cli/main.py` — the call site was `:8004-8017`, now `:8102-8118`):159 ```python160 if can_passthrough_rg and stats and _selected_route_supports_rg_passthrough(...):161 exit_code = rg_backend.search_passthrough(passthrough_paths, pattern, config=config)162 sys.exit(exit_code)163 ```164 This hands the **entire** search to a live `rg` subprocess — `RipgrepBackend._build_cmd` appends rg's own `--stats` flag when `config.stats` is set (`backends/ripgrep_backend.py:805-806`, unchanged this pass) — and exits on rg's own exit code, entirely bypassing everything `search_command` would otherwise do downstream: its own `_emit_stats()` `[stats] backend=... reason=...` line (`grep -n "def _emit_stats" src/tensor_grep/cli/main.py` — was `:8371-8407`, now starts `:8471`), the `--debug` routing echo (`grep -n "routing.backend=" src/tensor_grep/cli/main.py` — was `:8040-8043`, now around `:8142-8146`), and — the reported symptom — the `is_empty` branch's defaulted-scope note (`grep -n "_write_defaulted_scope_note" src/tensor_grep/cli/main.py` — was `:8437-8476`, now around `:8608-8611`).165- `_selected_route_supports_rg_passthrough` (`grep -n "^def _selected_route_supports_rg_passthrough" src/tensor_grep/cli/main.py` — was `:5451-5462`, now `:5551-5562`) requires `Pipeline.selected_backend_name == "RipgrepBackend"`, which `core/pipeline.py`'s backend-selection `__init__` picks via `elif rg_available: self.backend = rg_backend; selected_backend_reason = "rg_default_fast_path"` (`core/pipeline.py:358-359`, essentially unchanged from the cited `:356-359`) whenever `rg_backend.is_available()` is `True`. That in turn is `RipgrepBackend.is_available()` (`backends/ripgrep_backend.py:45`, unchanged this pass) → `resolve_ripgrep_binary()` (`grep -n "^def resolve_ripgrep_binary" src/tensor_grep/cli/runtime_paths.py` — was `:561-591`, now starts `:576`) — a pure **environment** probe: `TG_RG_PATH` env var, then `shutil.which("rg"/"rg.exe")` on `PATH`, then an in-tree fallback directory that is **gitignored** (`ripgrep-*/` in `.gitignore`) and absent from a fresh checkout on any platform (only the zip `benchmarks/rg.zip` is committed, and only a separate *test* helper — `tests/helpers/rg_parity.py::resolve_pinned_rg_binary` — knows how to unpack it; production code never touches the zip).166- **There is no `sys.platform`/`os.name` conditional anywhere in this chain.** The Windows-vs-Linux CI split is caused entirely by whether a real `rg`/`rg.exe` happens to be resolvable on `PATH` inside each OS leg of the `test-python` CI job (`grep -n "^ test-python:" .github/workflows/ci.yml` — `:360`, close to the cited `:361-430`), which installs **no** ripgrep package on any OS — contrast the `native-build-smoke`/`smoke`/`benchmark` jobs, which explicitly `apt-get install ripgrep` / `brew install ripgrep` (`grep -n "install ripgrep" .github/workflows/ci.yml` — was `:673-685,780-783`, now `:698-699` (native-build-smoke) and `:799` (benchmark-regression)).167- **Paired proof (same source tree, one variable flipped, 2026-07-30, Windows):** with a real `rg.exe` resolvable on `PATH`, `tg search NO_MATCH_ZZZ --stats` (no PATH argument) prints rg's own `0 matches / 0 matched lines / ...` stats block, no scope note, exit 1. With `PATH` stripped so `resolve_ripgrep_binary()` returns `None`, the identical invocation instead prints tg's own `[stats] backend=CPUBackend reason=cpu_python_regex_prefilter` line **and** the `is_empty` branch's `note: no PATH was given...`, still exit 1. One boolean (`rg_available`) is the entire mechanism.168- **Why only `--stats` XPASSed, not `--ast`/`--rank`/`--semantic`:** those three are hard-excluded from `_can_passthrough_rg` regardless of `rg` availability (see the exclusions above); `--stats` is the one flag in that family with no such categorical veto.169- **Ruled out:** the native Rust `tg` binary (`resolve_native_tg_binary`) is never reachable for `--stats` on either platform — `_can_delegate_to_native_tg_search`'s `unsupported_flags` explicitly lists `--stats` (`bootstrap.py:613`, unchanged this pass), and the `TG_RUST_FIRST_SEARCH` OR-branch (`grep -n "_prefer_rust_first_search() and not _requires_full_cli" src/tensor_grep/cli/bootstrap.py` — was `:1525`, now `:1645`) requires `not _requires_full_cli(...)`, which is always `False` for `--stats` (`_TG_ONLY_SEARCH_FLAGS`, `bootstrap.py:50`, unchanged this pass). `cli/mcp_server.py` has no `can_passthrough_rg`/`search_passthrough` shortcut at all — this divergence is CLI-only.170- **Downstream blast radius found while sweeping — CLOSED in this tree; the "still open / zero quiet mentions" text this bullet carried was FALSE.** The same `can_passthrough_rg` fork also gates the plain, non-stats internal passthrough (`grep -n "if can_passthrough_rg:" src/tensor_grep/cli/main.py` — was `:8037-8042`, now `:8401`), and BOTH passthrough call sites — the plain (`if not stats:`) branch and the `--stats` branch, `grep -n "search_passthrough" src/tensor_grep/cli/main.py` (`:8405`/`:8494` as of this pass) — funnel through the one method that now honors `--quiet`: `RipgrepBackend.search_passthrough` (`grep -n "def search_passthrough" src/tensor_grep/backends/ripgrep_backend.py` — `:478`) appends rg's `-q` at `:511` when `config.quiet` is set. It is deliberately NOT inside the shared `_build_cmd` (`:531`): `_build_cmd` has FOUR consumers — `search()` (`:80`, parses `--json`), `_search_files_with_matches()` (`:287`, parses `-l`), `_search_counts()` (`:406`, parses `--count`) — and only `search_passthrough` streams rg's output; `-q` makes rg print NOTHING, so a parsing consumer would report a false zero-match on a matching file plus an exit-code violation (the in-code hazard comment at `:492-510` records the measured rg behavior). Placement is pinned by `tests/unit/test_quiet_survives_rg_passthrough.py`: the fix arm asserts `-q` reaches the streaming route's argv, the control arms assert the three parsing consumers never receive `-q` and that a non-quiet search is never silenced. Do not re-propose moving `-q` into `_build_cmd`. (Honesty note: no test combines `--stats` AND `--quiet` specifically; coverage is at the `search_passthrough` choke point both branches share.)171- **Confirm on CI in one line**, run inside the `test-python` job matrix on each OS (no test framework needed):172 ```173 python -c "from tensor_grep.cli.runtime_paths import resolve_ripgrep_binary as r; print(r())"174 ```175 A real path on one OS and `None` on the other reproduces the whole divergence directly.176177## The walk-ceiling fast-refuse: 3 doors, 2 constants, 1 value (A9, v1.92.3/#702)178179Before #702, the plain flag-less `bootstrap._run_rg_passthrough` path (`grep -n "^def _run_rg_passthrough" src/tensor_grep/cli/bootstrap.py` — was `:1088`, now `:1421` — the front180door a bare `tg search PATTERN` with no scoping flags hits, *before* `main.py`'s Typer app is ever181reached) had **no walk ceiling at all**. `main.py`'s three vendored/workspace/large-root refusal guards182never ran for this path, so an unscoped search on a large defaulted-path root silently walked unbounded183until it hit the 60s `TG_RG_TIMEOUT_SECONDS` subprocess backstop — natively reproduced, not a WSL184filesystem artifact.185186The fix is one constant, enforced coherently across **3 doors**, not three independent numbers that can187drift apart:188189- **The si190191…(truncated)