You ratified (A): build symbol-level sight — so Plato can answer "who references symbol S," not just "who imports file F." The frontier pass says: (A) isn't one build. It's three separable pieces with very different costs, and the tidy "just unify on SCIP" story welds them together in a way that hides a trap.

Frontier verdict: SOUND — with caveats. SCIP is the fitness-matched default; the caveat is that the recommendation smuggles three different costs under one word.
The reframe: split "unify on SCIP" into three decisions. The cheap half's fitness must not carry in the expensive half's lock-in.

The three pieces of (A)

have most of it
C#/Python — the cheap win. SCIP already hands Plato the symbol-level references. Plato throws them away at exactly one line — a file-pair de-dup that forgets which symbol caused the edge. Surfacing it needs no new dependency.
↘ go deeper — the chokepoint
src/adapters/scip-core/index.mjs · buildScipGraph(). Nodes are file paths; edge de-dup key is ${consumer}\0${producer} (no symbol); the edge literal never carries occ.symbol, even though the loop reads occ.symbol/occ.symbol_roles right there. Downstream reach.mjs has zero symbol awareness (grep-verified: 0 hits). Minimal change: re-key de-dup on (consumer, producer, symbol), attach the symbol, add a symbol filter to reach. Caution: the only prior attempt to surface SCIP symbols in Plato (value-flow-edge.mjs) is 55KB, C#-only, and unwired into any tool (grep-verified). "One line" is a lower bound, not the real effort.
thin — extend, don't adopt
JS/TS — the trap. "Adopt scip-typescript" would replace a parser Plato owns and trusts with an external Node indexer that runs out of memory on big repos — for Plato's own home language. The parser we already have visits the imported name and just discards it; keeping it is a ~10-line change.
↘ go deeper — why extend-ours wins
JS/TS uses the in-process oxc witness (edge-completeness.mjs), not SCIP. Its ImportDeclaration visitor already walks specifiers but only special-cases createRequire (grep-verified). Retaining ImportSpecifier.imported.name is self-contained, zero new dependency — but non-compiler-verified (no cross-check that the name is truly exported, the one thing SCIP would add). "We already ingest SCIP" is the availability signal, not a fitness argument, for the one language where we own a better-fit tool.
build — first-class
The fence — the real work. "These are all the references to S" is wrong 30–60% of the time unless you fence it: dynamic dispatch, reflection, DI containers, codegen, serialization all hide references no static index can see. Plato has this fence pattern at the file level; the symbol level needs its own — and that's a genuine build, not free. Without it, symbol-reach manufactures the exact false "all-clear" Plato exists to kill.
↘ go deeper — the 13 blind axes + contract
Blind axes needing a fence string (mirroring inbound-fence.mjs → buildInboundCompletenessLabel): virtual/interface dispatch · reflection · DI/string registries · serialization · codegen/macros/partials · cross-language FFI · framework attribute magic · non-graph SFC hosts (.razor/.cshtml/.vue/.astro) · unresolved/any-typed occurrences · local-scheme/external omissions · unindexed/parse-failed files · definition-vs-reference role gap · test-only/stale coverage. Contract: never a bare "these are all references" — always PARTIAL / CANNOT-CERTIFY / none-found-but-grep-to-confirm. Evidence: static call-graph recall is 39–70% vs runtime truth; Roslyn (scip-dotnet's base) is imprecise on interface dispatch both directions.

The recommended sequence

  1. Run four cheap probes first — before committing build effort. They're investigation (free), and they settle the two risks that could otherwise sink the work.
    ↘ the four probes
    1. Role-bit determinism — histogram symbol_roles over the DePrism .scip fixture (are references cleanly tagged, or is the re-key non-deterministic?). 2. Currency latency — cold index time + delta after a one-line commit (the big one). 3. oxc-vs-scip-typescript spike — lines to extend oxc vs scip-typescript index time/RSS on Plato's own repo. 4. Virtual-dispatch false-complete — an interface + a concrete impl called only via the interface; if "who references ConcreteImpl.Method" misses the dispatching caller, the fence is proven mandatory.
  2. Ship the C#/Python symbol-surfacing win + the fence together — the cheap, high-fitness piece, never without its honesty layer.
  3. Extend the oxc parser for JS/TS (~10 lines) — do not adopt scip-typescript.
  4. Measure currency before going per-query at symbol granularity. If a trivial commit forces a slow full re-index, the currency layer may need an incremental substrate (stack-graphs) — a bigger decision we don't walk into blind.

The honest hard problems

Currency / latency (the real one). Plato re-indexes the whole repo on every commit (currency = HEAD-SHA match, grep-verified). At symbol granularity that could make "provably current" either slow-on-every-query or silently-stale — your cardinal sin. Unmeasured → Probe 2.
False-complete. Symbol-reach lying on dispatch/reflection/DI refs. Mitigated by the fence; Probe 4 proves the failure concretely so the fence isn't optional.
Determinism. The symbol re-key assumes clean SCIP role-bits; some occurrences may carry none. Unverified → Probe 1. And "what counts as a reference" (import? write?) is an un-ratified product decision.

Your calls

1 · Greenlight the C#/Python symbol-surfacing win? The cheap, high-fitness piece.
2 · JS/TS: extend our own parser (recommended) or adopt scip-typescript? I recommend extend-ours — the trap is real and grep-verified.
3 · Approve the symbol-level fence as a first-class build? The thing that keeps symbol-reach from lying.
4 · Run the four probes before building? Recommended — cheap, and they resolve the currency + determinism risks.
The cheap half's fitness (SCIP for C#/Python) must not smuggle in the expensive half's lock-in (an external toolchain for JS/TS). Three decisions, gated on four probes — not one "unify on SCIP" move.