From d963d4a78201f2456a7bacf83831d4f8da041bd6 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 22:47:30 -0400 Subject: [PATCH] chore(schema)!: collapse the schema version back to 2.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both version constants read 2.1.0 — the JSON envelope (src/schema/emit.ts) and the Neo4j contract (src/build/neo4j/schema.ts). Intermediate 2.x versions are not meaningful while the schema design is still settling, and every analyzer will re-baseline together later, so there is one schema version and it is 2.0.0. Comments that named 2.1.0 as a version boundary now reference the issue that introduced the behaviour (#92, anonymous callables) instead of a version that no longer exists. The Neo4j version gate is a plain inequality (`shouldForceFullUpsert`), so a database written by a 2.1.0 build is treated as a mismatch and repopulated by one full upsert on the next push. No manual migration. --- schema.neo4j.json | 2 +- src/build/neo4j/project.ts | 4 ++-- src/build/neo4j/schema.ts | 6 +++--- src/schema/emit.ts | 2 +- src/schema/homing.ts | 8 ++++---- src/schema/schema.ts | 6 +++--- test/anonymous-callables.test.ts | 6 +++--- test/schema-v2.test.ts | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/schema.neo4j.json b/schema.neo4j.json index a943322..4add5c5 100644 --- a/schema.neo4j.json +++ b/schema.neo4j.json @@ -1,5 +1,5 @@ { - "schema_version": "2.1.0", + "schema_version": "2.0.0", "generator": "codeanalyzer-typescript", "marker_labels": [], "node_labels": [ diff --git a/src/build/neo4j/project.ts b/src/build/neo4j/project.ts index e3149a3..6d7fc78 100644 --- a/src/build/neo4j/project.ts +++ b/src/build/neo4j/project.ts @@ -139,7 +139,7 @@ export function project(app: TSAnalysis, _appName?: string): GraphRows { for (const ext of Object.values(root.external_symbols ?? {})) { b.node([CAN, "TSExternal"], "id", ext.id, prune({ id: ext.id, kind: "external", name: ext.name, module: ext.module })); } - // 2.1.0: `synthesized_callables` is mostly a compatibility index (old id → tree id) whose targets + // #92: `synthesized_callables` is mostly a compatibility index (old id → tree id) whose targets // are already projected as tree nodes. Only the residual fallback entries — a signature no // provider could name, recognisable because the map key IS the entry's own id — still need a // standalone node, so call-graph edges pointing at them do not dangle. @@ -193,7 +193,7 @@ const ANON_SIG = /\.$/; function projectCallable(b: RowBuilder, c: TSCallable, owner: NodeRef, ownerRel: string, fileKey: string, source: string): void { // An unnamed callable carries :TSAnonymousCallable alongside :TSCallable — one node, two labels, - // reached by ordinary containment. That is what keeps pre-2.1.0 MATCH (:TSAnonymousCallable) + // reached by ordinary containment. That is what keeps older MATCH (:TSAnonymousCallable) // queries working and puts these nodes on the snapshot wipe's containment walk (issue #75). const labels = ANON_SIG.test(c.signature) ? [CAN, "TSCallable", "TSAnonymousCallable"] : [CAN, "TSCallable"]; const node = b.node(labels, "id", c.id, callableProps(c, fileKey, source)); diff --git a/src/build/neo4j/schema.ts b/src/build/neo4j/schema.ts index 4be019d..bb46939 100644 --- a/src/build/neo4j/schema.ts +++ b/src/build/neo4j/schema.ts @@ -19,7 +19,7 @@ * SCHEMA_VERSION: MAJOR on a breaking change (renamed/removed label, relationship or key), MINOR * on additive. v2 is a MAJOR bump from v1 (keys moved signature→can:// id; labels reshaped). */ -export const SCHEMA_VERSION = "2.1.0"; +export const SCHEMA_VERSION = "2.0.0"; export type PropType = "string" | "integer" | "float" | "boolean" | "string[]" | "integer[]"; @@ -67,7 +67,7 @@ export const NODE_LABELS: NodeLabel[] = [ // Repository-artifact layer (#101, python PR #160 parity): language-NEUTRAL labels — the // deliberate exception to TS-prefixing, so sibling analyzers MERGE onto the same // :Artifact/:Package/:ConfigKey nodes. Edges that stay this analyzer's own claim keep the TS_ - // prefix. Additive within 2.1.0 — SCHEMA_VERSION moves only when every analyzer re-baselines + // prefix. Additive within 2.0.0 — SCHEMA_VERSION moves only when every analyzer re-baselines // together. { label: "Artifact", @@ -163,7 +163,7 @@ export const NODE_LABELS: NodeLabel[] = [ }, { label: "TSExternal", mergeLabel: CAN, key: "id", properties: { ...COMMON, name: "string", module: "string" } }, { - // 2.1.0: a marker label carried *alongside* :TSCallable by an unnamed arrow / function + // #92: a marker label carried *alongside* :TSCallable by an unnamed arrow / function // expression, which is now a real tree node reached by TS_DECLARES from its enclosing // callable. It is no longer a node kind of its own — the property set is TSCallable's — but // the label is retained so existing MATCH (:TSAnonymousCallable) queries keep resolving. diff --git a/src/schema/emit.ts b/src/schema/emit.ts index dc35091..4434583 100644 --- a/src/schema/emit.ts +++ b/src/schema/emit.ts @@ -32,7 +32,7 @@ import { resolveLiteralConfigUses } from "../semantic_analysis/configUse"; import { widenConfigUses } from "../dataflow/configUse"; const LANGUAGE = "typescript"; -const SCHEMA_VERSION = "2.1.0"; +const SCHEMA_VERSION = "2.0.0"; const ANALYZER_NAME = "codeanalyzer-typescript"; /** Highest analysis level this emitter populates today (L1 tree, L2 call graph, L3/L4 dataflow). */ const MAX_IMPLEMENTED = 4; diff --git a/src/schema/homing.ts b/src/schema/homing.ts index 7eea938..3231d34 100644 --- a/src/schema/homing.ts +++ b/src/schema/homing.ts @@ -3,7 +3,7 @@ * application root, so the no-dangling rule holds. * * - `homeExternals`: external library call targets → `can://…/@external//` nodes. - * - `homeSynthesized`: the 2.1.0 anonymous-callable compatibility index (pre-2.1.0 id → the + * - `homeSynthesized`: the #92 anonymous-callable compatibility index (the older id → the * tree id that replaced it), plus residual fallback nodes for signatures no provider could * name (recognizable because the map key equals the entry's own id). * @@ -42,11 +42,11 @@ export function homeExternals(app: AnalysisInternal, appId: string, idBySig: Map } /** - * The compatibility index for anonymous callables (schema 2.1.0). + * The compatibility index for anonymous callables (#92). * * Anonymous callables are real nodes in the containment tree, signed positionally * (`.`) and reachable by containment. This map is not a node - * registry: it maps the **pre-2.1.0 id** of each anonymous callable — `@:`, + * registry: it maps the **older id** of each anonymous callable — `@:`, * derived from the old `:` signature — onto the tree id that replaced it, * so a consumer holding an old id can still resolve it. * @@ -65,7 +65,7 @@ export function homeSynthesized(app: AnalysisInternal, appId: string, idBySig: M const m = /^(.*?)((?:\.)+)$/.exec(sig); if (!m) continue; const host = idBySig.get(m[1] as string); - if (!host) continue; // module-level anonymous callable — no resolvable pre-2.1.0 id + if (!host) continue; // module-level anonymous callable — no resolvable older id const last = /$/.exec(sig) as RegExpExecArray; out[`${host}@${last[1]}:${last[2]}`] = { id, kind: "callable" }; } diff --git a/src/schema/schema.ts b/src/schema/schema.ts index 5ec034e..ac7d4ae 100644 --- a/src/schema/schema.ts +++ b/src/schema/schema.ts @@ -456,7 +456,7 @@ export interface TSExternalSymbol { } // A first-party anonymous callback a call-graph builder resolved as an edge endpoint but could -// not name against the symbol table (a residual-fallback safety net; since 2.1.0 the tree names +// not name against the symbol table (a residual-fallback safety net; since #92 the tree names // anonymous callables positionally, so this map is normally empty). The map key IS the // synthesized signature, so an edge `source`/`target` byte-matches it like a real signature. export interface TSSynthesizedCallable { @@ -491,7 +491,7 @@ export interface AnalysisInternal { // ---------------------------------------------------------------------------------------------- export interface TSAnalysis { - schema_version: string; // "2.1.0" + schema_version: string; // "2.0.0" language: string; // "typescript" max_level: number; // highest level populated; consumers read this, not key-sniffing k_limit?: number; // access-path depth bound for the L3/L4 dataflow (present at L3+) @@ -522,7 +522,7 @@ export interface TSApplication { config_reads: TSConfigRead[]; // TS-additive (parity): edge endpoints outside the containment tree need an id home. external_symbols?: Record; // L2 — library call targets, keyed by id - // L2 — 2.1.0 compatibility index: pre-2.1.0 anonymous-callable id → the tree id that replaced + // L2 — #92 compatibility index: the older anonymous-callable id → the tree id that replaced // it. Entries whose key equals their own `id` are the residual fallback nodes for signatures no // provider could name. synthesized_callables?: Record; diff --git a/test/anonymous-callables.test.ts b/test/anonymous-callables.test.ts index 20b2ca4..afb611c 100644 --- a/test/anonymous-callables.test.ts +++ b/test/anonymous-callables.test.ts @@ -1,5 +1,5 @@ /** - * Issue #92 (schema 2.1.0): an unnamed arrow / function expression is a callable in its own right. + * Issue #92: an unnamed arrow / function expression is a callable in its own right. * It is tree-contained under its enclosing callable with a durable positional signature segment * (``), carries its own body/cfg/cdg/ddg and formal-in vertices, and owns the call * sites that used to be attributed to the callable that merely encloses it. @@ -136,8 +136,8 @@ describe("anonymous callables are first-class (issue #92)", () => { } }); - test("the envelope declares schema 2.1.0", () => { - expect(application.schema_version).toBe("2.1.0"); + test("the envelope declares schema 2.0.0", () => { + expect(application.schema_version).toBe("2.0.0"); }); }); diff --git a/test/schema-v2.test.ts b/test/schema-v2.test.ts index ac7f528..a158282 100644 --- a/test/schema-v2.test.ts +++ b/test/schema-v2.test.ts @@ -84,7 +84,7 @@ function allIds(): string[] { describe("schema v2 — L1 envelope", () => { test("root envelope matches the canonical shape", () => { - expect(v2.schema_version).toBe("2.1.0"); + expect(v2.schema_version).toBe("2.0.0"); expect(v2.language).toBe("typescript"); expect(v2.max_level).toBe(1); expect(Object.keys(root).sort()).toEqual([