GitNexus/gitnexus/bench/emit-persistence
Abhigyan Patwari 9df1e98350
fix(routes): scope inline Express handlers and report api_impact lower bound (#3643)
* fix(routes): scope inline Express handlers and report api_impact lower bound

Inline Express handlers are anonymous Function nodes named by position, but
the route facts only recorded a handler name for bare identifiers. Routes
with inline handlers therefore had no handlerSymbolId: every route in a file
got the union of its handlers' response keys and every flow starting there.

Record the positional name for inline handlers, read response keys from the
resolved handler when a file registers several routes, and let processes
link exactly by handlerSymbolId as they already do for named handlers.

Count, per route, the files that pass its URL to a callee not proven to be
a fetch wrapper (excluding proven consumers and the route's own file). The
count is stored as Route.unverifiedConsumers; api_impact reports
epistemic: 'lower-bound' with that count when it is non-zero. Older indexes
without the column fall back to the previous query.

Closes #3488

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(routes): match unverified consumers once per URL

Memoize route matching per distinct fetch URL, skip the proven-consumer
scan when no unproven call matches a route, and build the Route query's
optional columns from a list instead of two booleans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(group): keep inline handlers attributed to their registrar

Inline Express handlers now carry their positional Function name in the
route facts so ingestion can resolve a handlerSymbolId. Group HTTP
contracts read the same facts, but that name is a graph identity rather
than a symbol name the contract lookup can match, so the provider lost its
enclosing-registrar attribution. Ignore the name for inline handlers there.

Rebaseline the two bench guards the branch moved:
- emit-persistence: route.csv gains the header-only unverifiedConsumers
  field; stripping it restores the prior fingerprint exactly.
- receiver-resolution: the new fixture's req.query.path read on an untyped
  inline-handler parameter adds one receiver-unresolved read (191 -> 192).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-10-20 17:44:08 +01:00
..
baselines-streaming.json fix(rename): extend safe refactoring and flow coverage across languages (#3557) 2026-10-13 08:38:17 +03:00
baselines.json fix(routes): scope inline Express handlers and report api_impact lower bound (#3643) 2026-10-20 17:44:08 +01:00
measure-streaming.mjs perf(cfg): streaming/chunked PDG graph emit for full-kernel-scale repos (#2202) (#2216) 2026-06-16 05:04:10 +01:00
measure.mjs perf(lbug): cut graph-DB emit/persistence wall time (#2203) (#2215) 2026-06-15 19:40:59 +01:00
README.md perf(lbug): cut graph-DB emit/persistence wall time (#2203) (#2215) 2026-06-15 19:40:59 +01:00

Emit-persistence bench (#2203)

Build-free throughput + byte-identity guard for the CSV-generation half of the graph-DB persistence pipeline (streamAllCSVsToDisk), which dominates large-repo analyze wall time alongside parsing (issue #2203).

# from gitnexus/
node --import tsx bench/emit-persistence/measure.mjs           # print one JSON line
node --import tsx bench/emit-persistence/measure.mjs --check    # gate vs baselines.json

What it measures

A synthetic KnowledgeGraph (files + functions + classes + 4 edge types across the File→Function, File→Class, Function→Function label pairs) at two scales:

  • elapsed_ms_small / elapsed_ms_large — median wall-clock over REPS runs of streamAllCSVsToDisk.
  • scaling_ratio — (t_large/t_small)/(LARGE/SMALL); ~1.0 is linear. The --check gate fails if it exceeds scaling_budget (catches an O(n²) re-regression in the emit/routing path).
  • fingerprint — order-independent sha256 over every emitted CSV line (node CSVs + per-FROM→TO-label-pair rel CSVs). This is the byte-identity gate: the U2 (direct per-pair routing) and U3 (per-row microtask elimination) optimisations must not change graph content, and any future change that does fails --check. Byte-identity holds for all quote-free ids; for an id containing a " the router intentionally diverges from — and is more correct than — the legacy regex oracle (see src/core/lbug/rel-pair-routing.ts).

What it does NOT measure

  • The LadybugDB COPY half. Bulk loading needs a live writable DB connection, so it can't run build-free. Its per-stage timing lives in the runtime PROF_LBUG_LOAD=1 breakdown ([lbug-load prof] csv-emit=… copy-nodes=… copy-rels=… fallback=… total=…) and is exercised end-to-end by the integration round-trip tests (test/integration/basicblock-roundtrip.test.ts, lbug-core-adapter.test.ts).
  • Content extraction. Bench nodes have no backing source files, so the content column is empty — emit cost here reflects the CSV machinery (routing, escaping, buffering, disk writes), not file reads.
  • At-scale absolute numbers. The real postgres / kernel-fs/ wall (issue #2203's table) is a maintainer-run measurement; this synthetic bench is the reproducible regression guard, not a substitute for those runs.

Deferred follow-up

Parallelising the COPY loop (PARALLEL=false is load-bearing; LadybugDB is single-writer) is out of scope for #2203 pending empirical validation of concurrent-COPY support — the PROF_LBUG_LOAD breakdown is the prerequisite that shows whether COPY is the dominant cost worth that risk.

Regenerating the baseline

node --import tsx bench/emit-persistence/measure.mjs    # copy fingerprint + ratio into baselines.json