# Band-2 resolver generalization — approaches (proposal, pre-plan)

audience: AI coding agents first. Imperative, BLUF. This is a brainstorm step-3 artifact (approaches +
recommendation + open steering question), NOT a committed design. Do not implement before the plan cycle.

## Problem [MEASURED]

The band-2 proactive resolver pulls a cross-file imported symbol into review scope ONLY if its name matches a
payments keyword regex (`claim|lock|guard|settle|payout|…`). Against the shipped Shape-B cell it matches
**0/18** imports — the canonical builder `buildSessionPayload` (insecure default `enforce_2fa ?? false`) never
enters scope, so band-1 never sees it (`docs/validation/2026-06-17-band2-trigger-gap-shipped.md`). The
detection half is solved: builder-in-scope → band-1 3/3 (`2026-06-17-band2-crossfile-spike.md`). **The
resolver/trigger is the whole game.** Goal: pull the right cross-file symbols across ANY domain, no keyword list.

## Success criteria (from the spike + SoT)

1. Catches the Shape-B canonical across ≥3 non-payments domains (n≥3 cells → a recall RATE, not n=1).
2. No domain-specific keyword list (the recall-spike verdict: keyword-gating is dead beyond payments).
3. Precision does not regress — scope growth must not bury the canonical or blow the token budget.
4. Discovered by convention, no registry (project principle).

## Approaches

### A — Pull every first-party CALLED import (budget-capped)
Resolve every import the target actually invokes to a file inside the repo (skip `node_modules`); concatenate
into scope up to a token budget; band-1 reviews the union.

| Dimension | Assessment |
|---|---|
| Robustness | High — no name dependence; catches any imported-default shape |
| Precision | Medium — scope grows; relies on band-1 to not drown (spike: handled concatenation fine at 2 files) |
| Cost | Medium-high — more tokens/call; budget cap bounds it but may truncate deep graphs |
| Reversibility | two-way door — pure scope policy in gate.py; swap freely |

**Weakness:** unbounded fan-in (a route calling 20 helpers) can exceed budget. You CANNOT "avoid dropping the canonical" — not knowing which symbol is the canonical is the whole premise. The achievable invariant is the inverse (see Budget invariant below).

### B — Broaden the keyword list
Add `session|token|cookie|permission|role|tenant|2fa|password|…`.

| Dimension | Assessment |
|---|---|
| Robustness | Low — moves the deadline to the next domain; the spike already rejected this |
| Reversibility | two-way door |

**Weakness:** structurally the same hack; fails success-criterion 2. Listed only to reject.

### C — Type/dataflow-aware (return flows to a security sink)
Pull an imported symbol if its return value flows into a security sink (JWT sign, DB query, auth decision,
HTTP response). Uses the TS oracle's type info.

| Dimension | Assessment |
|---|---|
| Robustness | High and principled — targets exactly the security-relevant subset |
| Precision | High — pulls less than A, only sink-reaching symbols |
| Cost | High to BUILD — needs type-aware flow analysis on top of the oracle; heavier than a scope policy |
| Reversibility | one-way-ish — couples the resolver to the oracle's type layer |

**Weakness:** large build; the "sink" set is itself a list that can be domain-incomplete (a softer version of B's problem).

### D — Approach A, ranked + budget-prioritized (hybrid)
A's pull set, but order by a lightweight, domain-agnostic relevance signal (e.g. symbol is CALLED with the
route's auth/identity inputs as args; or returns an object spread into a security-relevant payload) so the
budget cap drops the LEAST-likely-relevant first. Ranking REDUCES truncation harm; it does NOT license a clean
verdict when truncation still drops something (see Budget invariant — the dropped-set still forces UNRELIABLE).

| Dimension | Assessment |
|---|---|
| Robustness | High — A's completeness |
| Precision | High — ranking keeps scope lean under budget |
| Cost | Medium — ranking heuristic is cheaper than C's full type-flow |
| Reversibility | two-way door — ranking is a sort key, swappable |

**Weakness:** the ranking heuristic needs its own validation (does it ever rank the canonical below the cut?).

## Recommended: D (A as the v1 fallback)

Start with **A** to restore coverage immediately (it is the smallest change that satisfies criteria 1–2 and is
already partly validated by the spike's 2-file concatenation). Add **D**'s ranking the moment a real cell
exceeds the token budget — that is the trigger that makes ranking earn its place (deletion test: without
ranking, an over-budget graph drops first-party imports arbitrarily and the whole file goes UNRELIABLE more
often; with ranking, fewer files hit truncation so fewer go UNRELIABLE). Defer **C** unless D's heuristic
proves domain-incomplete on the n≥3 corpus. Reject **B**.

## Budget invariant (no-false-clean) [DESIGN — binding]

You CANNOT promise "never drop the canonical": not knowing which imported symbol is the canonical is the entire
premise of the gate. So the resolver MUST NOT claim that as its safety property. The achievable invariant is the
inverse, and it is the SAME discipline already shipped in `gate.py:oracle_status` (memory GAP-A: unresolved
imports → UNRELIABLE, never a clean pass):

> If a file's first-party called-import set exceeds the token budget and the resolver drops ANY of them, the
> file's result is **UNRELIABLE**, not clean. List the dropped symbols; mark the file degraded; never read a
> SILENT band-1 on a budget-truncated scope as a clean pass.

Ranking (D) only changes WHICH imports survive truncation — it never removes the obligation to flag UNRELIABLE
when truncation happens. The remedy for an UNRELIABLE file is the operator's (raise budget, split the route),
exactly as the oracle's remedy is "run from the full repo / fix aliases." This invariant is a hard gate on any
"generalized resolver" claim: a resolver that silently truncates is a regression to the founding sin
(false-clean), not a generalization.

## Open steering question (one-way-door check, for the user)

Token budget vs completeness is the real trade. **A pulls more and costs more per call; C builds a type-flow
analyzer for precision.** Default chosen here = A→D (cheap, reversible, validated direction). Confirm before
the plan cycle commits, or steer toward C if precision/cost at scale is the priority over time-to-coverage.

## Validation gate (before any "generalized" claim)

n≥3 Shape-B cells across distinct domains (auth, tenant-isolation, one more), RED on vuln / GREEN on safe,
canonical pulled into scope and caught k≥3. No keyword list anywhere in the resolver path. Precision measured
(findings:canonical ratio) vs the current payments path on the existing cells (no regression). Budget invariant
honored: a deliberately over-budget fixture (fan-in > cap) MUST report UNRELIABLE for the truncated file, never
a clean SILENT — assert this as an anti-canary, the same way `oracle_status` is tested.
