refactor(schema)!: native v2 model — one model family + per-run passes - #97
Merged
Conversation
…on (#96) Deep-equal analysis.json goldens at -a 1..4 plus sorted-line cypher goldens for sample-app, dataflow-app, and anon-app, captured with the default union provider. Transition-scoped: deleted at Stage 4 teardown.
…sses (#96) Stage 1 of the native-model rewrite (docs/design/specs/native-v2-model.md): - src/schema/schema.ts is now the native v2 shape: v2-bucketed, span-only containers (types{}/functions{}/fields{}, callables{}, body{}), omit- instead-of-null leaves, and the INTERNAL fields (call_sites, abs_path, cache trio) that never reach the wire. - builders.ts constructs that shape directly; the id-free tree is what the cache round-trips (ids embed --app-name, so they are per-run). - Python-parity passes: assignIds (can:// ids + sig→id map + collision gate), l1Body (call_sites → body{} call nodes, wholesale per-run rebuild that also makes repeated emission idempotent), heritage (extends_ids/ implements_ids), homing (externals + 2.1.0 synthesized index), l2Callees (callee null→id + call-graph re-identification + dangling gate). - emit.ts no longer reshapes anything: it runs the passes, assembles the envelope, and returns a deep internal-field-stripped wire copy. - callGraph.ts / dataflow readers walk the new buckets via the shared forEachCallable/forEachType walkers; RTA's class index keeps its historical module/namespace-scope reach. - Dead v1-only baggage deleted: per-node code, local_variables, value, container flat line/col ints, module_name/file_path. Wire-stable: all 12 pre-rewrite goldens (analysis.json -a 1..4 × 3 fixtures + cypher) deep-equal; full suite green.
Stages 2+3 of the native-model rewrite, landed together because the pass
chain is order-coupled (l2 passes join on assignIds output, the attach on
both) — relocating it piecemeal would have made two incoherent halves:
- finalizeAnalysis (src/schema/emit.ts) is the per-run pass spine — ids,
body, heritage, homing, callee backfill, call-graph re-identification,
dataflow attach, envelope, internal-field strip — and analyze() runs it
(python-parity: core.py owns the passes). It returns both views:
{application (wire), internal (live tree), idBySig, collisions, dangling}.
- The L3/L4 tree-attach moves to src/dataflow/attach.ts and takes the
program-graph IR directly — the stage that computes the graphs also
writes them onto the tree.
- serialize.ts no longer reshapes anything: JSON path writes the envelope,
Neo4j path projects it. src/schema/v2/ is now model types + a transition
shim (toV2/toV2Detailed → finalizeAnalysis) that Stage 4 retires.
- saveCache stays ahead of the passes: the cached tree is id-free
(--app-name is per-invocation) and holds only source-of-truth layers.
Wire-stable: 12 goldens deep-equal; full suite + typecheck green.
Stage 4 (internal-only breaking surface; the wire is unchanged): - src/schema/v2/ is gone. schema.ts now also holds the wire envelope (TSAnalysis), the application root (TSApplication — python's PyApplication analog), and the wire edge shapes (TSCallGraphEdge, TSParamEdge). The internal working set is AnalysisInternal. - toV2/toV2Detailed shims retired; finalizeAnalysis(app, pg, opts) is the one pass-runner, and the program-graph IR rides AnalysisResult instead of the application aggregate. - The Neo4j projection reads typed fields (no more V2Node attribute probing); prop semantics preserved bit-for-bit (goldens). - The analysis cache stores only the id-free tree (the write-only cached call_graph is dropped). - CLAUDE.md architecture/dir-map rewritten for the native model (also carries the pending caveman-compression of the guide); the local SCHEMA_DECISIONS.md gains the native-model decision table. Wire-stable: 12 goldens deep-equal; full suite + typecheck green.
analyze() returns AnalysisResult; tests read .application (wire), .internal (working set), .program_graphs (IR) instead of re-running the retired toV2/toV2Detailed shims. The v1-superset gate becomes the id- registration gate (its v1 premise no longer exists; completeness is carried by the transition goldens and the standing conformance gates).
The rewrite is complete and wire-stability is proven; the deep-equal goldens and their gate leave with the transform they guarded. The standing schema conformance, monotonicity (L1⊆L2⊆L3⊆L4), and Neo4j gates carry coverage from here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #96. Design record: docs/design/specs/native-v2-model.md.
What
Retires the two-model architecture: the v1
TSApplicationcompute model and the ~720-line emit-time v1→v2 transform (src/schema/v2/emit.ts+dataflow.ts) are gone.src/schema/schema.tsnow IS schema v2 — envelopeTSAnalysis→ rootTSApplication→TSModule/TSType/TSCallable/TSField/TSBodyNode— built directly by the builders, mirroring codeanalyzer-python's post-rewrite nativepy_schema.pyarchitecture.Per-run passes stamp the derived layers (python parity:
assign_ids.pyet al.):assignIds—can://ids + sig→id map + collision gate (per-run because ids embed--app-namewhile the cache round-trips the tree)l1Body— internalcall_sites→body{}call nodes (wholesale per-run rebuild; makes the pass chain idempotent)heritage—extends_ids/implements_ids(resolved-only)homing+l2Callees— external/synthesized endpoint homing, calleenull→id, call-graph re-identification + dangling gatesrc/dataflow/attach.ts— the stage that computes the program graphs also writes them onto the treefinalizeAnalysis(src/schema/emit.ts) runs the spine — invoked byanalyze(), which now returnsAnalysisResult{application (wire), internal, program_graphs, idBySig, collisions, dangling}. Serialization is dumb: JSON path writes the envelope verbatim; Neo4j projects the same envelope with typed field access (no more attribute probing). INTERNAL fields (call_sites,abs_path, cache trio) ride the model and are stripped by key in one place. The analysis cache stores only the id-free builder tree.Wire stability (the gate)
analysis.jsonand the Neo4j projection are unchanged — schema_version stays 2.1.0, no SDK impact. Proven by transition goldens captured from the branch base: deep-equalanalysis.jsonat-a 1|2|3|4× 3 fixtures (sample-app, dataflow-app, anon-app, union provider) + sorted-linegraph.cypher, green on every stage commit and retired with the transform they guarded (the standing conformance + monotonicity + Neo4j gates carry coverage from here). The goldens caught one real reconstruction bug during Stage 1 (a clamp that appended an ellipsis the original never did).Commits = the spec's stages
test(goldens)— pin the pre-rewrite wirerefactor(schema)— Stage 1: native tree + passesrefactor(core)— Stages 2+3 (landed together; the pass chain is order-coupled): core owns the spine, attach moves to dataflow, emission thinnedrefactor(schema)!— Stage 4 teardown: terminalTS*names,src/schema/v2/deleted, typed Neo4j projectiontest— suite re-pointed at the terminalanalyze()APItest(goldens)— transition harness retireddocs(design)— spec recorded as implemented!marks the internal API break (analyzer-internal only; the wire is unchanged).Verification
tsc --noEmitcleanbun run buildcompiles; binary smoke-tested (-a 2on sample-app: schema 2.1.0, 7 modules, 57 edges)bun run gen:schema:schema.neo4j.jsonbyte-stablegit grep -l "toV2|V2Application|V2Root|V2Node"→ 0 filesRelease (1.1.0) is deliberately not part of this PR.