Skip to content

fix(scripts): make the sql Date-binding audit precise and crash-proof - #6340

Merged
waleedlatif1 merged 5 commits into
stagingfrom
fix/sql-date-binding-precision
Aug 6, 2026
Merged

fix(scripts): make the sql Date-binding audit precise and crash-proof#6340
waleedlatif1 merged 5 commits into
stagingfrom
fix/sql-date-binding-precision

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

scripts/check-sql-date-binding.ts (shipped in #6337) catches a real production bug class — a bare Date interpolated into a drizzle raw sql template reaches the postgres driver unserialized and throws ERR_INVALID_ARG_TYPE. The detection goal is kept unchanged. This PR fixes five ways the detector could fail CI on correct code, plus three false negatives that fall out of the same fix.

False positives fixed

1. The tag was matched by the bare identifier name sql. That cannot tell drizzle's sql tag from postgres-js's own client tag (const sql = postgres(url)), which serializes Dates correctly. Two live call sites already sit in scanned directories:

  • apps/sim/app/api/tools/postgresql/utils.ts:20const sql = postgres({...}) with **6 live interpolating sql\`` templates** (~lines 233/241/255/268/280/307). Adding a Date` there is correct code the old detector would reject.
  • packages/db/scripts/reconcile-workspace-storage.ts:21 — same pattern; passed only by luck (its queries are literal-only).
  • scripts/setup/probes.ts:24 — a third, in the directory this PR newly scans.

The tag is now resolved to an actual import … from 'drizzle-orm' binding. A locally declared const sql = postgres(...) is no longer treated as the drizzle tag.

2. collectDateNames over-approximated scope file-wide. Two sub-problems:

  • TSPropertySignature fields typed Date were absorbed, so a single interface R { start: Date } marked the identifier start as a Date for the whole file.
  • Bindings crossed function boundaries, so const now = new Date() in one function made ${now} in another — where now is a number — a violation.

The audit found 97 of 179 (54%) files containing an interpolating sql template already bind at least one Date-typed name, across 282 distinct names dominated by exactly the collision-prone ones (now 335 bindings, timestamp 123, createdAt 83, date 75, updatedAt 70, start 24, expiresAt 24, end 22, cutoff 15). A drizzle-scoped re-measure on this branch gives 88/142 files. Either way this was a when-not-if CI break.

Bindings are now tracked in a lexical scope chain (function-level), with declaration-order-independent fix-point resolution retained for const b = a chains. Destructured Date params — function q({ since }: { since: Date }) — were previously caught via the TSPropertySignature path; that true positive is preserved by handling destructuring patterns explicitly, and now also resolves through a named interface (function q({ since }: Range)).

3. A parse failure crashed the run. errorRecovery: true does not cover missing Babel plugins, and main() had no try/catch, so a decorator, a class accessor, JSX in a .ts file, or any pre-existing syntax error failed CI with a stack trace. The per-file parse() is now wrapped; an unparseable file is reported as visibly skipped (printed to stderr with the parser message and a count), not silently swallowed. The decorators plugin is added.

4. The // sql-date-bound: <reason> escape hatch was unusable for multi-line templates. It only inspected the line above the interpolated expression, which for a multi-line template sits inside the SQL string — the marker would be sent to Postgres as junk. This is the shape of the actual outage site (cleanup-stale-executions/route.ts's CASE…END block), so the escape hatch did not work where it was most needed. The annotation is now also accepted above the enclosing TaggedTemplateExpression or its enclosing statement. An empty reason is still rejected at every anchor.

5. Coverage hole: SCAN_DIRS was [apps, packages]; the root scripts/ directory was never scanned. Added (12,899 → 12,977 files).

False negatives closed (free, from fix 1)

Aliased imports (import { sql as raw }), namespace imports (d.sql\…`), and the matching d.sql.param(...)` member form are now detected. All were previously invisible.

Verification

Each fix has a test that is red before the change and green after — verified by reverting each fix individually and watching the specific test fail (6 reverts, 6 targeted failures).

End-to-end on the full repo:

  • Exits 0 across 12,977 files.
  • Still catches the original outage: reverting sql.param(now, asyncJobs.startedAt) at cleanup-stale-executions/route.ts:250 is reported at line 254.
  • Does not flag a Date injected into the postgres-js templates in apps/sim/app/api/tools/postgresql/utils.ts or scripts/setup/probes.ts.
  • Does not flag a cross-function now shadow or an interface { start: Date } file. Injecting both into the real apps/sim/background/cleanup-logs.ts: the old detector reports 2 violations, the new one reports 0.

Runtime: 5.46s → 4.07s over a larger file set (12,899 → 12,977 files). Skipping files with no drizzle-orm import before the scope pass more than pays for the extra analysis.

Known limitations (unchanged or accepted)

These are not covered — this PR does not claim full coverage:

  • Cross-file imported Date constants (import { CUTOFF } from './constants').
  • Function-call returns: ${getCutoff()}.
  • Array/member access: ${dates[0]}, ${row.startedAt}, ${this.now}.
  • Dates flowing through object properties or array elements.
  • Class property bindings, which the old pass collected. Since isDateExpression only resolves bare identifiers and class fields are read as this.x, collecting them could only ever produce false positives, never a catch — so they are dropped deliberately.
  • Block-level scoping is approximated at function level. That direction only over-approximates within a single function, which was already the case.

Follow-up in this PR: script unit tests removed

Per review discussion, the repo does not carry unit tests for scripts/ — only 3 of 34 scripts had them. This PR now also removes all three, so the convention is consistent:

  • Deletes scripts/check-sql-date-binding.test.ts, scripts/check-migrations-safety.test.ts, and scripts/check-tool-request-boundary.test.ts
  • Drops the bun test … half of the check:sql-date-binding and check:tool-request-boundary gates in package.json — both invoked files this PR deletes, so CI would fail otherwise. check:migrations never referenced its test
  • Removes findSqlDateBindingViolations (a pure test wrapper) and un-exports analyzeSource, SCAN_DIRS, lintSql, and findToolRequestBoundaryViolations — every one had zero references outside its own script and existed only so the tests could reach them

All three gates verified running and passing standalone afterwards:

check:sql-date-binding       ✓ 12974 files bind every sql-template Date through a column encoder
check:tool-request-boundary  ✓ production tool requests are materialized only by the shared transport
check:migrations             ✓ No new migrations to check.

The detector still reports the original outage site when its sql.param is reverted, and no YAML/JSON/script reference to any deleted file remains. Supersedes #6343, which is closed.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 6, 2026 9:33pm

Request Review

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Changes affect dev/CI lint scripts only, not runtime app code; risk is limited to false CI passes/fails on the audit rules.

Overview
Hardens the drizzle sql Date-binding CI check so it targets real violations without breaking on correct postgres-js sql usage, and removes the three scripts/*.test.ts suites plus the bun test steps from related package.json gates.

The Date audit now ties tagged templates to drizzle-orm imports (not any local sql name), tracks function-scoped Date bindings (including destructured params), accepts // sql-date-bound: above the template or statement, warns on parse skips instead of crashing, scans scripts/, and skips files with no drizzle import. lintSql and findToolRequestBoundaryViolations are no longer exported—only the standalone scripts run in CI.

Reviewed by Cursor Bugbot for commit 7d05018. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes the SQL Date-binding audit resolve actual Drizzle bindings, track Date values by function scope, tolerate parser failures, support multiline suppression annotations, and scan root scripts. It also removes script-only unit tests and their test-oriented exports while keeping the standalone repository checks runnable.

  • Distinguishes Drizzle SQL tags from postgres-js client tags and recognizes aliases, namespaces, and dynamic imports.
  • Adds function-level binding analysis, destructured Date parameter support, and parse-error reporting.
  • Removes three script test files and updates package scripts to invoke only the production audits.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
scripts/check-sql-date-binding.ts Reworks tag resolution, Date-binding scope analysis, annotation handling, parse recovery, and scan coverage without an eligible blocking issue.
package.json Removes references to deleted script tests while preserving direct execution of the production checks.
scripts/check-migrations-safety.ts Makes the migration linter internal after deleting its only external test consumer.
scripts/check-tool-request-boundary.ts Makes the boundary-analysis helper internal after deleting its only external test consumer.

Reviews (5): Last reviewed commit: "chore(scripts): drop the script unit tes..." | Re-trigger Greptile

Comment thread scripts/check-sql-date-binding.test.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread scripts/check-sql-date-binding.ts
Comment thread scripts/check-sql-date-binding.ts
Resolve the drizzle `sql` tag from its import binding, scope Date bindings
lexically, tolerate unparseable files, accept the allow annotation above a
multi-line template, and scan the root scripts directory.
@waleedlatif1
waleedlatif1 force-pushed the fix/sql-date-binding-precision branch from 4aba547 to d828a11 Compare August 6, 2026 20:46
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread scripts/check-sql-date-binding.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 78cea08. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7d05018. Configure here.

@waleedlatif1
waleedlatif1 merged commit 8c49d35 into staging Aug 6, 2026
4 of 5 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/sql-date-binding-precision branch August 6, 2026 21:31
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