Skip to content

Transform graph testing & verification - #40

Open
galdre wants to merge 11 commits into
mainfrom
galdre/test-transforms/v1
Open

Transform graph testing & verification#40
galdre wants to merge 11 commits into
mainfrom
galdre/test-transforms/v1

Conversation

@galdre

@galdre galdre commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added transform-test command with inputs and run subcommands for validating transform/card behavior.
    • Supports suite-based YAML runs, file and glob SQL assertions (error/warn), expected-output diffs, and --ignore-columns.
    • Improved CLI parsing for repeated flags and comma-separated ID lists; command help reflects the new command.
  • Bug Fixes
    • More resilient handling of server error payloads when error details aren’t simple strings.
  • Documentation
    • Added/expanded transform-test and transform-test-plan skill documentation, including checklists and a checks catalog.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the mb transform-test command with inputs and run subcommands, YAML suite and SQL assertion parsing, subgraph API execution, result rendering, supporting schemas/runtime helpers, and transform-test planning documentation.

Changes

Transform-test command feature

Layer / File(s) Summary
Domain schemas and runtime support
src/domain/transform-test-run.ts, src/domain/transform-test-run.test.ts, src/commands/parse-id.ts, src/commands/runtime.ts, src/runtime/citty.ts, src/runtime/upload.ts, src/core/http/errors.ts
Defines test-run schemas and views, adds ID-list and repeated-flag parsing, exposes raw arguments, supports multipart file reads, and accepts structured HTTP errors.
Assertion and suite configuration
src/commands/transform-test/assert.ts, assert.test.ts, suite.ts, suite.test.ts
Parses file/glob assertions, resolves SQL files, and normalizes YAML suites with inline/file assertions and severity handling.
Subgraph inputs, execution, and rendering
src/commands/transform-test/subgraph.ts, subgraph.test.ts
Parses targets and fixtures, fetches inputs, builds multipart requests, submits runs, computes outcomes, and renders results.
CLI command wiring
src/commands/transform-test/index.ts, inputs.ts, run.ts, src/main.ts, src/runtime/command-help.test.ts, tests/e2e/*.test.ts
Registers the command and subcommands, composes suite and flag inputs, and updates command and end-to-end expectations.
Transform-test planning documentation
skill-data/transform-test/SKILL.md, skill-data/transform-test-plan/*, skill-data/data-workflow/references/building-clean-tables.md
Documents transform-test usage and adds planning, checklist, assertion-catalog, fixture, severity, and workflow guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as transform-test run
  participant Config as Suite and assertion parsers
  participant Runner as subgraph execution
  participant API as Metabase API
  CLI->>Config: load suite and resolve assertions
  CLI->>Runner: provide normalized run arguments
  Runner->>Runner: build multipart form
  Runner->>API: POST subgraph test run
  API-->>Runner: return TestRunResult
  Runner->>CLI: render result and report failure status
Loading

Suggested reviewers: ranquild

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main theme: adding transform-test tooling plus related testing and verification docs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch galdre/test-transforms/v1
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch galdre/test-transforms/v1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@galdre
galdre marked this pull request as ready for review July 8, 2026 18:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/domain/transform-test-run.test.ts (1)

13-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use full deep equality assertions for schema parse tests.

Tests 1, 2, and 4 use .success checks and piecemeal property assertions instead of toEqual. Per coding guidelines, assertions must be full deep equality where possible.

♻️ Proposed refactor
   it("accepts a schema-qualified input table", () => {
-    expect(TestRunInput.safeParse(base).success).toBe(true);
+    expect(TestRunInput.parse(base)).toEqual(base);
   });

   it("accepts a null schema (engines without schemas)", () => {
-    const parsed = TestRunInput.safeParse({ ...base, schema: null });
-    expect(parsed.success).toBe(true);
-    expect(parsed.data?.schema).toBeNull();
+    expect(TestRunInput.parse({ ...base, schema: null })).toEqual({ ...base, schema: null });
   });

   it("rejects a missing schema key", () => {
     const { schema: _schema, ...rest } = base;
     expect(TestRunInput.safeParse(rest).success).toBe(false);
   });

   it("compact pick preserves a null schema", () => {
-    expect(TestRunInputCompact.parse({ ...base, schema: null }).schema).toBeNull();
+    expect(TestRunInputCompact.parse({ ...base, schema: null })).toEqual({
+      table_id: 229,
+      schema: null,
+      name: "orders",
+      columns: ["id", "total"],
+    });
   });

Additionally, consider adding tests for AssertionResult, TestRunResult, and their compact variants to match the coverage depth of TestRunInput.

As per coding guidelines: "Assertions in unit tests must be full deep equality where possible (toEqual(full object/array)), not piecemeal property checks."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domain/transform-test-run.test.ts` around lines 13 - 30, The schema parse
tests currently rely on `.success` and single-property checks instead of full
object comparisons, so update the assertions in the `TestRunInput` and
`TestRunInputCompact` cases to use deep equality with the parsed object where
applicable. In the `TestRunInput.safeParse` and `TestRunInputCompact.parse`
tests, assert the entire parsed result/object rather than checking `success` or
only `schema`. Also add similar full-equality coverage for `AssertionResult`,
`TestRunResult`, and their compact variants to match the same validation depth.

Source: Coding guidelines

src/commands/transform-test/assert.ts (2)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Name the discriminated union members per coding guidelines.

The guideline requires inline object types in unions to be named with interface or type. Extract each member:

♻️ Proposed refactor
-export type AssertToken = { kind: "file"; path: string } | { kind: "glob"; pattern: string };
+export interface AssertFileToken {
+  kind: "file";
+  path: string;
+}
+
+export interface AssertGlobToken {
+  kind: "glob";
+  pattern: string;
+}
+
+export type AssertToken = AssertFileToken | AssertGlobToken;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/transform-test/assert.ts` at line 15, The AssertToken union in
assert.ts uses inline object members, which violates the naming guideline.
Extract each union member into separately named interfaces or type aliases, then
update AssertToken to reference those named members while keeping the same
discriminant field (`kind`) and existing properties (`path`, `pattern`).

Source: Coding guidelines


14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove WHAT comments per coding guidelines.

These comments restate what the code already expresses clearly. The guideline says to avoid comments unless the WHY is non-obvious and to never write WHAT comments.

♻️ Proposed refactor
-// An --assert value is a `.sql` file or a glob of them; inline SQL is not supported.
 export type AssertToken = AssertFileToken | AssertGlobToken;
 
-// basename with `*` ⇒ glob; plain `.sql` ⇒ file; anything else rejected.
 export function classifyAssertToken(token: string): AssertToken {

And at line 94:

-// Each `.sql` file → one assertion, named by basename without extension; severity defaults to error.
 export async function resolveAssertions(tokens: AssertToken[]): Promise<AssertionDef[]> {

Also applies to: 17-17, 94-94

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/transform-test/assert.ts` at line 14, Remove the WHAT-style
comments in assert.ts that simply restate the code, including the top-level note
about --assert inputs and the similar comment near the assert handling logic;
keep only comments that explain non-obvious WHY, and use the surrounding symbols
in this file (such as the assert parsing/validation code and related helpers) to
preserve readability without redundant commentary.

Source: Coding guidelines

src/commands/transform-test/run.ts (1)

143-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider exporting and reusing DEFAULT_TARGET_TYPE instead of the inline "transform" literal.

Line 145 uses ?? "transform" as a type-narrowing fallback that never triggers at runtime (when idGiven is true, targetType is always defined from line 143). The literal "transform" duplicates DEFAULT_TARGET_TYPE from subgraph.ts (currently not exported). Exporting and reusing it would eliminate the repeated magic literal.

♻️ Optional refactor

In subgraph.ts:

-const DEFAULT_TARGET_TYPE: TargetType = "transform";
+export const DEFAULT_TARGET_TYPE: TargetType = "transform";

In run.ts:

 import {
+  DEFAULT_TARGET_TYPE,
   parseColumnList,
   parseInputPairs,
   parseTargetType,
   runSubgraph,
   type SubgraphRunArgs,
   targetLabels,
   targetTypeFlag,
 } from "./subgraph";

Then:

-      ? parseId(args.id, targetLabels(targetType ?? "transform").positionalLabel)
+      ? parseId(args.id, targetLabels(targetType ?? DEFAULT_TARGET_TYPE).positionalLabel)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/transform-test/run.ts` around lines 143 - 146, The `run.ts`
target parsing uses an inline "transform" fallback in `targetLabels(targetType
?? "transform")`, but that fallback is unreachable and duplicates the default
from `subgraph.ts`. Export `DEFAULT_TARGET_TYPE` from `subgraph.ts` and reuse it
in `run.ts` for both `parseTargetType` defaults and `targetLabels`, replacing
the magic string and keeping `parseId`/`targetLabels` aligned with the shared
constant.

Source: Coding guidelines

src/commands/transform-test/index.ts (1)

1-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use defineCommandGroup for this dispatcher. src/commands/transform-test/index.ts should follow the other parent command indexes so the shared augment metadata — including explicit capabilities: null — is attached automatically.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/transform-test/index.ts` around lines 1 - 13, The transform-test
dispatcher is using the wrong command helper, so the parent command metadata
augmentation is not being applied. Update the default export in transform-test’s
index module to use defineCommandGroup instead of defineCommand, matching the
other parent command indexes so shared augment metadata such as explicit
capabilities: null is attached automatically.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/transform-test/suite.test.ts`:
- Line 101: The tests in parseSuite should assert the exact ConfigError message
instead of only matching the error type. Update the failing rejection checks in
suite.test.ts around parseSuite so they verify the known stable error string for
each case, using the parseSuite helper and ConfigError as the reference points.

In `@src/core/http/errors.ts`:
- Around line 41-43: parseEnvelopeMessage is still treating envelope.error as an
opaque value, so object-shaped errors like { message: "..." } fall through to
later fallbacks. Update the error extraction logic in the envelope parsing flow
to detect when envelope.error is an object and prefer its message field before
checking error-message or generic fallback text, while keeping the existing
z.unknown().optional() schema intact.

---

Nitpick comments:
In `@src/commands/transform-test/assert.ts`:
- Line 15: The AssertToken union in assert.ts uses inline object members, which
violates the naming guideline. Extract each union member into separately named
interfaces or type aliases, then update AssertToken to reference those named
members while keeping the same discriminant field (`kind`) and existing
properties (`path`, `pattern`).
- Line 14: Remove the WHAT-style comments in assert.ts that simply restate the
code, including the top-level note about --assert inputs and the similar comment
near the assert handling logic; keep only comments that explain non-obvious WHY,
and use the surrounding symbols in this file (such as the assert
parsing/validation code and related helpers) to preserve readability without
redundant commentary.

In `@src/commands/transform-test/index.ts`:
- Around line 1-13: The transform-test dispatcher is using the wrong command
helper, so the parent command metadata augmentation is not being applied. Update
the default export in transform-test’s index module to use defineCommandGroup
instead of defineCommand, matching the other parent command indexes so shared
augment metadata such as explicit capabilities: null is attached automatically.

In `@src/commands/transform-test/run.ts`:
- Around line 143-146: The `run.ts` target parsing uses an inline "transform"
fallback in `targetLabels(targetType ?? "transform")`, but that fallback is
unreachable and duplicates the default from `subgraph.ts`. Export
`DEFAULT_TARGET_TYPE` from `subgraph.ts` and reuse it in `run.ts` for both
`parseTargetType` defaults and `targetLabels`, replacing the magic string and
keeping `parseId`/`targetLabels` aligned with the shared constant.

In `@src/domain/transform-test-run.test.ts`:
- Around line 13-30: The schema parse tests currently rely on `.success` and
single-property checks instead of full object comparisons, so update the
assertions in the `TestRunInput` and `TestRunInputCompact` cases to use deep
equality with the parsed object where applicable. In the
`TestRunInput.safeParse` and `TestRunInputCompact.parse` tests, assert the
entire parsed result/object rather than checking `success` or only `schema`.
Also add similar full-equality coverage for `AssertionResult`, `TestRunResult`,
and their compact variants to match the same validation depth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 721583b8-8d38-4c56-85f6-2e53e93d5f87

📥 Commits

Reviewing files that changed from the base of the PR and between f31632d and c9976fc.

📒 Files selected for processing (19)
  • skill-data/transform-test/SKILL.md
  • src/commands/parse-id.ts
  • src/commands/runtime.ts
  • src/commands/transform-test/assert.test.ts
  • src/commands/transform-test/assert.ts
  • src/commands/transform-test/index.ts
  • src/commands/transform-test/inputs.ts
  • src/commands/transform-test/run.ts
  • src/commands/transform-test/subgraph.test.ts
  • src/commands/transform-test/subgraph.ts
  • src/commands/transform-test/suite.test.ts
  • src/commands/transform-test/suite.ts
  • src/core/http/errors.ts
  • src/domain/transform-test-run.test.ts
  • src/domain/transform-test-run.ts
  • src/main.ts
  • src/runtime/citty.test.ts
  • src/runtime/citty.ts
  • src/runtime/upload.ts

Comment on lines +87 to +99
it("expands a glob to one assertion per matching .sql file, sorted by name", async () => {
const out = await resolveAssertions([{ kind: "glob", pattern: join(dir, "*.sql") }]);
expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
expect(out.every((a) => a.severity === "error")).toBe(true);
});

it("resolves multiple file tokens preserving order, each named by basename", async () => {
const out = await resolveAssertions([
{ kind: "file", path: join(dir, "has_rows.sql") },
{ kind: "file", path: join(dir, "no_negatives.sql") },
]);
expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use full deep equality assertions per coding guidelines.

The glob expansion and multiple-file tests check only a.name and a.severity piecemeal. The guideline requires full deep equality with toEqual where possible. The SQL content is known from the temp files, so full assertions are feasible.

💚 Proposed fix for glob expansion test
   it("expands a glob to one assertion per matching .sql file, sorted by name", async () => {
     const out = await resolveAssertions([{ kind: "glob", pattern: join(dir, "*.sql") }]);
-    expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
-    expect(out.every((a) => a.severity === "error")).toBe(true);
+    expect(out).toEqual([
+      {
+        name: "has_rows",
+        sql: "SELECT * FROM test_output WHERE 1=0",
+        severity: "error",
+      },
+      {
+        name: "no_negatives",
+        sql: "SELECT * FROM test_output WHERE revenue < 0",
+        severity: "error",
+      },
+    ]);
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("expands a glob to one assertion per matching .sql file, sorted by name", async () => {
const out = await resolveAssertions([{ kind: "glob", pattern: join(dir, "*.sql") }]);
expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
expect(out.every((a) => a.severity === "error")).toBe(true);
});
it("resolves multiple file tokens preserving order, each named by basename", async () => {
const out = await resolveAssertions([
{ kind: "file", path: join(dir, "has_rows.sql") },
{ kind: "file", path: join(dir, "no_negatives.sql") },
]);
expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
});
it("expands a glob to one assertion per matching .sql file, sorted by name", async () => {
const out = await resolveAssertions([{ kind: "glob", pattern: join(dir, "*.sql") }]);
expect(out).toEqual([
{
name: "has_rows",
sql: "SELECT * FROM test_output WHERE 1=0",
severity: "error",
},
{
name: "no_negatives",
sql: "SELECT * FROM test_output WHERE revenue < 0",
severity: "error",
},
]);
});
it("resolves multiple file tokens preserving order, each named by basename", async () => {
const out = await resolveAssertions([
{ kind: "file", path: join(dir, "has_rows.sql") },
{ kind: "file", path: join(dir, "no_negatives.sql") },
]);
expect(out.map((a) => a.name)).toEqual(["has_rows", "no_negatives"]);
});

Source: Coding guidelines

assertions:
- name: bad
`;
await expect(parseSuite(yaml, "suite.yaml")).rejects.toThrow(ConfigError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert exact error messages per coding guidelines.

The guideline requires exact error strings when checking failures. Both rejection cases produce a known, stable ConfigError message — assert it.

💚 Proposed fix
   it("rejects an assertion with neither sql nor file", async () => {
     ...
-    await expect(parseSuite(yaml, "suite.yaml")).rejects.toThrow(ConfigError);
+    await expect(parseSuite(yaml, "suite.yaml")).rejects.toThrow(
+      "Suite assertion 'bad' must set exactly one of 'sql' or 'file'.",
+    );
   });

   it("rejects an assertion with both sql and file", async () => {
     ...
-    await expect(parseSuite(yaml, "suite.yaml")).rejects.toThrow(ConfigError);
+    await expect(parseSuite(yaml, "suite.yaml")).rejects.toThrow(
+      "Suite assertion 'bad' must set exactly one of 'sql' or 'file'.",
+    );
   });

Also applies to: 114-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/transform-test/suite.test.ts` at line 101, The tests in
parseSuite should assert the exact ConfigError message instead of only matching
the error type. Update the failing rejection checks in suite.test.ts around
parseSuite so they verify the known stable error string for each case, using the
parseSuite helper and ConfigError as the reference points.

Source: Coding guidelines

Comment thread src/core/http/errors.ts
Comment on lines +41 to +43
// `error` may be a structured object, not a string; accept any shape so a non-string
// doesn't break parsing the whole envelope.
error: z.unknown().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for test cases or API documentation showing structured error shapes
rg -n "error.*message" --type=ts src/core/http/ -g '!*.test.ts' -C3
rg -n "envelope\.error" --type=ts src/core/http/ -C3

Repository: metabase/metabase-cli

Length of output: 2015


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,280p' src/core/http/errors.ts
printf '\n---\n'
rg -n "parseEnvelopeMessage|formatErrorTree|error-message|specific-errors|via|errors:" src -C 2
printf '\n---\n'
rg -n "structured error|error-message|specific-errors|parseEnvelopeMessage" src -g '!*.snap' -C 3

Repository: metabase/metabase-cli

Length of output: 34950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for tests or fixtures that exercise error envelopes and structured error payloads.
fd -a -t f "errors" src test . | sed -n '1,120p'
printf '\n---\n'
rg -n "parseEnvelopeMessage|formatErrorTree|error-message|specific-errors|envelope\.error|via:" src test -g '!*.snap' -C 2

Repository: metabase/metabase-cli

Length of output: 7026


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the envelope schema and message extraction logic.
sed -n '1,260p' src/core/http/errors.ts

Repository: metabase/metabase-cli

Length of output: 7889


Unwrap object-shaped envelope.error values
parseEnvelopeMessage still drops structured envelope.error payloads like { message: "..." }. Extract error.message here before the fallback chain so those messages surface instead of falling through to error-message or generic text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/http/errors.ts` around lines 41 - 43, parseEnvelopeMessage is still
treating envelope.error as an opaque value, so object-shaped errors like {
message: "..." } fall through to later fallbacks. Update the error extraction
logic in the envelope parsing flow to detect when envelope.error is an object
and prefer its message field before checking error-message or generic fallback
text, while keeping the existing z.unknown().optional() schema intact.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@skill-data/transform-test-plan/references/checklist.md`:
- Around line 41-45: Update
skill-data/transform-test-plan/references/checklist.md lines 41-45 to require
full-expression analysis of filters, projections, default mappings, and declared
key relationships before classifying orphan behavior as dropped, NULL-preserved,
or default-row. Update skill-data/transform-test-plan/references/checks.md lines
146-153 to detect unmatched keys with an explicit key-based predicate rather
than checking whether a copied attribute is NULL, so legitimate nullable
attributes are not misclassified.

In `@skill-data/transform-test-plan/references/checks.md`:
- Around line 44-50: Update the A2 Conservation reconciliation guidance to
require an independent COUNT(*) comparison alongside additive-measure SUM
comparisons for every declared tie. Ensure the count subqueries use the same
input, output, and declared exclusions, and report mismatched row counts using
the existing IS DISTINCT FROM assertion pattern.

In `@skill-data/transform-test-plan/SKILL.md`:
- Line 84: Update the fenced directory-tree block in SKILL.md by adding the text
language tag to its opening fence, changing the untyped fence to a text fence
while leaving the directory listing unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c12c6af-2a71-4854-bf09-26f191cf4cea

📥 Commits

Reviewing files that changed from the base of the PR and between 218731b and fd8eb71.

📒 Files selected for processing (6)
  • skill-data/data-workflow/references/building-clean-tables.md
  • skill-data/transform-test-plan/SKILL.md
  • skill-data/transform-test-plan/references/checklist.md
  • skill-data/transform-test-plan/references/checks.md
  • skill-data/transform-test/SKILL.md
  • tests/e2e/skills.e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • skill-data/transform-test/SKILL.md

Comment on lines +41 to +45
6. **Orphan handling per join: drop / keep-with-NULLs / default row — and is
an orphan tolerated (warn) or forbidden (error)?** (C3, I5)
Detect: join type. INNER = drop, LEFT = keep-with-NULLs, COALESCE to a
sentinel = default row. Tolerated-vs-forbidden is the owner's call —
profiling says whether orphans exist today, not whether they're acceptable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make orphan handling depend on explicit key semantics, not heuristics.

The checklist and catalog can derive incorrect tests when join type or nullable copied columns are treated as definitive. Analyze filters, projections, default mappings, and the declared key relationship.

  • skill-data/transform-test-plan/references/checklist.md#L41-L45: require full-expression analysis before classifying drop, NULL preservation, or default-row behavior.
  • skill-data/transform-test-plan/references/checks.md#L146-L153: replace t.<copied attr> IS NULL with an explicit unmatched-key predicate that cannot flag legitimate NULL attributes.
📍 Affects 2 files
  • skill-data/transform-test-plan/references/checklist.md#L41-L45 (this comment)
  • skill-data/transform-test-plan/references/checks.md#L146-L153
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skill-data/transform-test-plan/references/checklist.md` around lines 41 - 45,
Update skill-data/transform-test-plan/references/checklist.md lines 41-45 to
require full-expression analysis of filters, projections, default mappings, and
declared key relationships before classifying orphan behavior as dropped,
NULL-preserved, or default-row. Update
skill-data/transform-test-plan/references/checks.md lines 146-153 to detect
unmatched keys with an explicit key-based predicate rather than checking whether
a copied attribute is NULL, so legitimate nullable attributes are not
misclassified.

Comment on lines +44 to +50
**A2 — Conservation reconciliation.** Row counts and additive-measure sums tie
from input to output; catches dropped rows and join double-counting at once.
- Applies: per declared tie (which input, which declared exclusions).
- Assert (error): independent scalar subqueries compared with
`IS DISTINCT FROM`, returning the mismatched pair:
`SELECT (SELECT SUM(t.m) FROM test_output t) AS output_sum,
(SELECT SUM(s.m) FROM <input> s) AS input_sum WHERE (…) IS DISTINCT FROM (…)`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Include row-count reconciliation, not only SUM comparisons.

A2 claims that row counts and additive-measure sums tie, but the specified assertion only compares SUM(...). Dropping zero- or NULL-valued rows can therefore pass the check. Add an independent COUNT(*) comparison for each declared tie, honoring exclusions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skill-data/transform-test-plan/references/checks.md` around lines 44 - 50,
Update the A2 Conservation reconciliation guidance to require an independent
COUNT(*) comparison alongside additive-measure SUM comparisons for every
declared tie. Ensure the count subqueries use the same input, output, and
declared exclusions, and report mismatched row counts using the existing IS
DISTINCT FROM assertion pattern.

cell is the null policy's only enforcement.
7. **Write the suites**, one directory per target:

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced directory tree.

The untyped fence triggers markdownlint MD040. Use text for this directory listing.

Proposed fix
-   ```
+   ```text
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 84-84: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skill-data/transform-test-plan/SKILL.md` at line 84, Update the fenced
directory-tree block in SKILL.md by adding the text language tag to its opening
fence, changing the untyped fence to a text fence while leaving the directory
listing unchanged.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant