-
Notifications
You must be signed in to change notification settings - Fork 91
feat: wire altimate-core 0.5.1 equivalence dialect + decidable into dbt review #928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -320,7 +320,13 @@ export function registerAll(): void { | |
| register("altimate_core.equivalence", async (params) => { | ||
| try { | ||
| const schema = schemaOrEmpty(params.schema_path, params.schema_context) | ||
| const raw = await core.checkEquivalence(params.sql1, params.sql2, schema) | ||
| // Pass the optional dialect hint so dialect-specific compiled warehouse SQL | ||
| // (e.g. Snowflake semi-structured `col:field`) parses and the pair is | ||
| // decidable instead of abstaining on a syntax error. Supported since | ||
| // altimate-core@0.5.1. Use `|| undefined` (not `??`) so the default empty | ||
| // string from ReviewConfig.dialect coerces to "no hint": the engine throws | ||
| // on an unknown dialect "", and "" must mean auto-detect, not a real dialect. | ||
| const raw = await core.checkEquivalence(params.sql1, params.sql2, schema, params.dialect || undefined) | ||
| const data = toData(raw) | ||
| return ok(true, data) | ||
| } catch (e) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [LOW · code-reviewer] Inconsistent null-coalescing behavior: 💡 Suggestion: Standardize on Confidence: 85/100 |
||
|
|
@@ -332,7 +338,7 @@ export function registerAll(): void { | |
| register("altimate_core.migration", async (params) => { | ||
| try { | ||
| // Build schema from old_ddl, analyze new_ddl against it | ||
| const schema = core.Schema.fromDdl(params.old_ddl, params.dialect || undefined) | ||
| const schema = core.Schema.fromDdl(params.old_ddl, params.dialect ?? undefined) | ||
| const raw = core.analyzeMigration(params.new_ddl, schema) | ||
| const data = toData(raw) | ||
| return ok(true, data) | ||
|
|
@@ -426,7 +432,7 @@ export function registerAll(): void { | |
| register("altimate_core.column_lineage", async (params) => { | ||
| try { | ||
| const schema = resolveSchema(params.schema_path, params.schema_context) | ||
| const raw = core.columnLineage(params.sql, params.dialect || undefined, schema ?? undefined) | ||
| const raw = core.columnLineage(params.sql, params.dialect ?? undefined, schema ?? undefined) | ||
| return ok(true, toData(raw)) | ||
| } catch (e) { | ||
| return fail(e) | ||
|
|
@@ -447,7 +453,7 @@ export function registerAll(): void { | |
| // 22. altimate_core.format | ||
| register("altimate_core.format", async (params) => { | ||
| try { | ||
| const raw = core.formatSql(params.sql, params.dialect || undefined) | ||
| const raw = core.formatSql(params.sql, params.dialect ?? undefined) | ||
| const data = toData(raw) | ||
| return ok(true, data) | ||
| } catch (e) { | ||
|
|
@@ -458,7 +464,7 @@ export function registerAll(): void { | |
| // 23. altimate_core.metadata | ||
| register("altimate_core.metadata", async (params) => { | ||
| try { | ||
| const raw = core.extractMetadata(params.sql, params.dialect || undefined) | ||
| const raw = core.extractMetadata(params.sql, params.dialect ?? undefined) | ||
| return ok(true, toData(raw)) | ||
| } catch (e) { | ||
| return fail(e) | ||
|
|
@@ -468,7 +474,7 @@ export function registerAll(): void { | |
| // 24. altimate_core.compare | ||
| register("altimate_core.compare", async (params) => { | ||
| try { | ||
| const raw = core.compareQueries(params.left_sql, params.right_sql, params.dialect || undefined) | ||
| const raw = core.compareQueries(params.left_sql, params.right_sql, params.dialect ?? undefined) | ||
| return ok(true, toData(raw)) | ||
| } catch (e) { | ||
| return fail(e) | ||
|
|
@@ -522,7 +528,7 @@ export function registerAll(): void { | |
| // 29. altimate_core.import_ddl — returns Schema, must serialize | ||
| register("altimate_core.import_ddl", async (params) => { | ||
| try { | ||
| const schema = core.importDdl(params.ddl, params.dialect || undefined) | ||
| const schema = core.importDdl(params.ddl, params.dialect ?? undefined) | ||
| const jsonObj = schema.toJson() | ||
| return ok(true, { success: true, schema: toData(jsonObj) }) | ||
| } catch (e) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| import { afterAll, beforeAll, describe, expect, test } from "bun:test" | ||
| import { writeFileSync } from "node:fs" | ||
| import path from "node:path" | ||
| import { runReview, DEFAULT_RUBRIC, DEFAULT_REVIEW_CONFIG, type ChangedFile } from "../../src/altimate/review" | ||
| import { createDispatcherRunner } from "../../src/altimate/review/runner" | ||
| import { registerAll } from "../../src/altimate/native/altimate-core" | ||
| import { tmpdir } from "../fixture/fixture" | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Full-stack E2E: dbt PR review pipeline → real Dispatcher → real altimate-core | ||
| // 0.5.1 engine. NO mocks. Exercises the complete chain that the dialect wiring | ||
| // (core 0.5.1 `dialect` arg) and `decidable` handling run through: | ||
| // | ||
| // runReview → semanticChangeLane → runner.equivalence(old, new, dialect) | ||
| // → Dispatcher → altimate_core.equivalence handler | ||
| // → core.checkEquivalence(sqlA, sqlB, schema, dialect) | ||
| // | ||
| // A real dbt manifest supplies the schema so the engine can resolve columns and | ||
| // actually DECIDE equivalence rather than abstain. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| describe("E2E: review pipeline + real engine equivalence (core 0.5.1)", () => { | ||
| beforeAll(async () => { | ||
| process.env.ALTIMATE_TELEMETRY_DISABLED = "true" | ||
| // The DispatcherRunner calls the REAL native handlers; register them in case | ||
| // another file reset the Dispatcher. | ||
| registerAll() | ||
| const { registerAllSql } = await import("../../src/altimate/native/sql/register") | ||
| registerAllSql() | ||
| }) | ||
| afterAll(() => { | ||
| delete process.env.ALTIMATE_TELEMETRY_DISABLED | ||
| }) | ||
|
|
||
| // A real manifest with typed columns so resolveSchema() yields a usable schema. | ||
| async function reviewerWithManifest() { | ||
| const tmp = await tmpdir() | ||
| const manifestPath = path.join(tmp.path, "manifest.json") | ||
| writeFileSync( | ||
| manifestPath, | ||
| JSON.stringify({ | ||
| metadata: { adapter_type: "snowflake" }, | ||
| nodes: { | ||
| "model.demo.orders": { | ||
| resource_type: "model", | ||
| name: "orders", | ||
| original_file_path: "models/marts/orders.sql", | ||
| config: { materialized: "table" }, | ||
| depends_on: { nodes: [] }, | ||
| columns: { | ||
| id: { name: "id", data_type: "integer" }, | ||
| amount: { name: "amount", data_type: "integer" }, | ||
| status: { name: "status", data_type: "varchar" }, | ||
| }, | ||
| }, | ||
| }, | ||
| sources: {}, | ||
| }), | ||
| ) | ||
| return { tmp, runner: createDispatcherRunner({ manifestPath }) } | ||
| } | ||
|
|
||
| async function review(oldSql: string, newSql: string, dialect = "snowflake") { | ||
| const { tmp, runner } = await reviewerWithManifest() | ||
| try { | ||
| const files: ChangedFile[] = [{ path: "models/marts/orders.sql", status: "modified", diff: "+changed" }] | ||
| const env = await runReview({ | ||
| changedFiles: files, | ||
| config: { ...DEFAULT_REVIEW_CONFIG, reviewers: ["semantic_change"], dialect, ai: false }, | ||
| rubric: DEFAULT_RUBRIC, | ||
| mode: "comment", | ||
| runner, | ||
| getContent: async (_f, side) => (side === "new" ? newSql : oldSql), | ||
| getCompiled: async (_f, side) => (side === "new" ? newSql : oldSql), | ||
| generatedAt: "2026-06-10T00:00:00Z", | ||
| }) | ||
| return env | ||
| } finally { | ||
| await tmp[Symbol.asyncDispose]?.() | ||
| } | ||
| } | ||
|
|
||
| const eqFindings = (env: Awaited<ReturnType<typeof review>>) => | ||
| env.findings.filter((f) => f.evidence?.tool === "altimate_core.equivalence") | ||
|
|
||
| test("real engine DECIDES a non-equivalent rewrite through the full pipeline", async () => { | ||
| // `amount > 5` vs `amount > 6` is a genuine row-changing predicate change. | ||
| const env = await review( | ||
| "select id from orders where amount > 5", | ||
| "select id from orders where amount > 6", | ||
| ) | ||
| const findings = eqFindings(env) | ||
| expect(findings.length).toBeGreaterThan(0) | ||
| const f = findings[0] | ||
| expect(f.category).toBe("semantic_change") | ||
| expect((f.evidence?.result as any)?.equivalent).toBe(false) | ||
| // The engine names the concrete predicate difference (not a vague abstention). | ||
| const diffs = (f.evidence?.result as any)?.differences ?? [] | ||
| expect(diffs.length).toBeGreaterThan(0) | ||
| }) | ||
|
|
||
| test("real engine PROVES an equivalent refactor → lane stays silent (no false positive)", async () => { | ||
| // AND-conjunct reorder is provably equivalent; the reviewer must not nitpick it. | ||
| const env = await review( | ||
| "select id from orders where amount > 5 and status = 'x'", | ||
| "select id from orders where status = 'x' and amount > 5", | ||
| ) | ||
| expect(eqFindings(env)).toEqual([]) | ||
| expect(env.verdict).toBe("APPROVE") | ||
| }) | ||
|
|
||
| test("identical compiled SQL → equivalence lane is skipped entirely", async () => { | ||
| const sql = "select id from orders where amount > 5" | ||
| const env = await review(sql, sql) | ||
| expect(eqFindings(env)).toEqual([]) | ||
| expect(env.verdict).toBe("APPROVE") | ||
| }) | ||
|
|
||
| test("column projection change (drop a column) is decided NOT equivalent", async () => { | ||
| const env = await review( | ||
| "select id, amount from orders", | ||
| "select id from orders", | ||
| ) | ||
| const findings = eqFindings(env) | ||
| expect(findings.length).toBeGreaterThan(0) | ||
| expect((findings[0].evidence?.result as any)?.equivalent).toBe(false) | ||
| }) | ||
|
|
||
| test("DEFAULT config dialect (empty string) still DECIDES — no engine throw", async () => { | ||
| // ReviewConfig.dialect defaults to "". The engine throws on an unknown dialect | ||
| // "", so without coercion the lane would abstain on EVERY change under the | ||
| // default config. This drives the full pipeline with dialect="" and asserts | ||
| // the non-equivalent change is still caught (decided), proving the coercion. | ||
| const env = await review( | ||
| "select id from orders where amount > 5", | ||
| "select id from orders where amount > 6", | ||
| "", // <- default ReviewConfig.dialect | ||
| ) | ||
| const findings = eqFindings(env) | ||
| expect(findings.length).toBeGreaterThan(0) | ||
| expect((findings[0].evidence?.result as any)?.equivalent).toBe(false) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[LOW · code-reviewer] The PR introduces a deliberate use of
|| undefinedinstead of?? undefinedto coerce empty strings toundefinedfor dialect handling; this may conflict with CLAUDE.md if it mandates??universally.💡 Suggestion: Verify that CLAUDE.md allows exceptions for dialect coercion logic. If CLAUDE.md requires
??everywhere, add a comment referencing this PR's rationale to justify the exception.Confidence: 80/100