Skip to content

fix: db_tables validation silently passes dropped/renamed/misconfigured schemas (closes #435) - #455

Closed
0xLeif wants to merge 3 commits into
mainfrom
fix/435-db-schema-validation
Closed

fix: db_tables validation silently passes dropped/renamed/misconfigured schemas (closes #435)#455
0xLeif wants to merge 3 commits into
mainfrom
fix/435-db-schema-validation

Conversation

@0xLeif

@0xLeif 0xLeif commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

db_tables validation silently passed in four situations:

  1. Dropped/renamed tables resurrected: table discovery was a CREATE-pattern scan, so DROP TABLE doomed and ALTER TABLE old_users RENAME TO users left doomed/old_users in the discovered set — specs referencing dead tables passed.
  2. Quoting/case/schema prefixes: "Users", `users`, USERS, and public.users were treated as different tables.
  3. Missing schema_dir / uncompilable schema_pattern: discovery returned an empty set and every declared table vacuously passed with no signal.
  4. Unreadable migration files: silently skipped, vanishing tables and potentially no-op'ing all DB validation.

Repro (fixture: /var/tmp/scratch/w2/435/)

-- migrations/001_init.sql
CREATE TABLE old_users (id INTEGER PRIMARY KEY);
CREATE TABLE doomed (id INTEGER);
-- migrations/002_migrate.sql
ALTER TABLE old_users RENAME TO users;
DROP TABLE doomed;

Spec declares db_tables: [old_users, doomed, users].

  • Before: all three pass — old_users and doomed are phantom-validated.
  • After: old_users and doomed are "DB table not found in schema" errors; users passes. Quoted/cased/qualified spellings ("USERS", public.users) match.

Root cause

get_schema_table_names used a naive CREATE-only regex scan over migration files with no replay semantics and no name normalization, and every failure mode of the discovery path (missing dir, bad regex, unreadable file) was designed to fail open to an empty set.

Fix

  • schema.rs: migrations replay in filename order via build_schema_with_retiredDROP TABLE and ALTER TABLE ... RENAME TO retire names (returned alongside the table map); normalize_table_name strips per-segment quoting and lowercases; schema_read_errors flags unreadable migration files and unenumerable schema dirs.
  • validator.rs: get_schema_table_names is replay-based (a schema_pattern scan can only ADD names, never resurrect retired ones); schema_config_problems (missing schema_dir / bad schema_pattern); schema_table_exists (normalized comparison, unqualified name matches schema-qualified discovered name); a db_tables-declaring spec with no configured schema_dir now warns visibly instead of skipping silently.
  • commands/mod.rs: run_validation surfaces schema config problems and schema read errors once, project-level, as hard errors.

Tests (all in-file, all passing)

test_get_schema_table_names_rename_and_drop_applied, test_schema_table_exists_quoting_case_and_schema_prefix, test_schema_config_problems_loud, test_validate_spec_db_tables_warn_when_no_schema_dir_and_error_on_phantom (validator.rs); test_quoted_and_schema_qualified_table_names, test_normalize_table_name, test_rename_and_drop_apply_to_quoted_names, test_schema_read_errors_flags_only_unreadable, test_schema_read_errors_flags_unenumerable_dir (schema.rs).

File overlap note

src/validator.rs (which contains the validator-side half of this fix) ships in PR #453 (fix/436-frontmatter-validation) — that issue most owns the file; the whole current file there includes these #435 changes. src/commands/mod.rs also contains the #420 "N warning(s) suppressed by .specsyncignore" visibility line, noted in the #420 PR. This PR ships src/schema.rs and src/commands/mod.rs in full.

Full suite: 1743 passed, 1 pre-existing root-only failure (unrelated). clippy --all-targets -- -D warnings clean.

Closes #435

0xLeif added 2 commits July 24, 2026 12:34
…oses #435)

Migrations now replay in filename order with DROP TABLE and
ALTER TABLE RENAME retiring old names (a CREATE-pattern rescan
cannot resurrect them); table names normalize quoting style and
case; unreadable schema files/dirs are hard errors instead of
silently disabling db_tables validation.
…ct-level (closes #435)

run_validation now fails loud on a missing schema_dir, an
uncompilable schema_pattern, and unreadable schema/migration
files (previously each silently disabled db_tables validation),
and makes .specsyncignore warnings plus the count of suppressed
warnings visible in every output format.
@0xLeif
0xLeif requested a review from a team as a code owner July 24, 2026 18:39
@0xLeif
0xLeif requested review from 0xGaspar, Kyntrin and tofu-ux July 24, 2026 18:39
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions 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.

❌ Corvin says...

      _
    <(;\  .oO(oh no...)
     |/(\
      \(\\
      " "\\

"Caw... validation failed..."

CI Summary

Check Status
Validate action.yml ✅ Passed
Packaged Action Consumer ❌ failure
Dependency Audit ✅ Passed
Code Coverage ❌ failure
Format Check ❌ failure
Docs Site ✅ Passed
Spec Validation ❌ failure
Tests (build, test, clippy) ❌ failure
VS Code Extension ✅ Passed

Powered by corvid-pet

@github-actions github-actions 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.

❌ Corvin says...

      _
    <(;\  .oO(oh no...)
     |/(\
      \(\\
      " "\\

"Caw... validation failed..."

CI Summary

Check Status
Validate action.yml ✅ Passed
Packaged Action Consumer ✅ Passed
Dependency Audit ✅ Passed
Code Coverage ✅ Passed
Format Check ✅ Passed
Docs Site ✅ Passed
Spec Validation ❌ failure
Tests (build, test, clippy) ✅ Passed
VS Code Extension ✅ Passed
📋 Spec Validation Details

❌ SpecSync: Failed

Metric Value
Specs checked 62
Passed 62
Errors 1
Warnings 0
File coverage 100% (105/105)
LOC coverage 100% (88024/88024)

Errors

.specsync/sdd.json

  • meaningful changed paths are not covered by an active change: specs/schema/context.md, specs/schema/requirements.md, specs/schema/schema.spec.md, specs/schema/tasks.md, specs/schema/testing.md, src/schema.rs, tests/integration/config.rs

Action Items

  • Review and fix -- meaningful changed paths are not covered by an active change: specs/schema/context.md, specs/schema/requirements.md, specs/schema/schema.spec.md, specs/schema/tasks.md, specs/schema/testing.md, src/schema.rs, tests/integration/config.rs

Generated by specsync · Run specsync check --format github to reproduce


Powered by corvid-pet

@0xGaspar 0xGaspar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving the code. The description contains a claim that isn't accurate, which I'd like corrected before merge.

Verified:

  • Merges cleanly into current main.
  • Merged with #449/#456/#457: 2070 unit + 335 integration tests, 0 failures.

Description issue. The Fix section says:

commands/mod.rs: run_validation surfaces schema config problems and schema read errors once, project-level, as hard errors.

and the overlap note says "This PR ships src/schema.rs and src/commands/mod.rs in full."

Against the shared branch point this PR changes only src/schema.rs (+1336/−183) and tests/integration/config.rs. It does not touch src/commands/mod.rs.

I chased this because it looked like it would leave schema_read_errors orphaned. It doesn't — main already calls it at src/commands/mod.rs:741, and schema::build_schema at :282, so the wiring exists and the fix is genuinely complete. No code change needed. But the description implies this PR delivers wiring it doesn't contain, so please correct it.

On the substance: replay semantics are clearly right. A CREATE-pattern scan that never processes DROP or RENAME doesn't just miss things, it actively resurrects dead tables and phantom-validates specs against them — worse than no check. Making build_schema_with_retired return retired names alongside the table map, so a schema_pattern scan can only ever ADD and never resurrect, is the correct invariant and worth keeping as an explicit comment in the code.

Failing open to an empty set on missing dir / bad regex / unreadable file was the deeper bug — four independent paths to vacuous success. Good that all four now signal.

One thing to watch: #453 ships a near-identical src/schema.rs — the two differ by exactly two lines, both #[allow(dead_code)] attributes. See my review on #453; those attributes are load-bearing in a way that I think indicates a problem there, not here. Whichever lands second will need a real rebase, not a blind take-theirs.

Approving; please fix the description.


Verified locally: branches fetched and test-merged against origin/main (a8e00a0-era), blob SHAs compared via git ls-tree, build/test run on the merged result. Findings below are from that mechanical check plus a read of the diff — not a line-by-line audit of every hunk.

@0xLeif

0xLeif commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by main 6.0 hygiene path (#484) / docs (#482). Pre-6.0 branch is conflicted; residual product fixes should be re-opened against current main (or slimmed via #483 if it is reworked to green).

@0xLeif 0xLeif closed this Jul 31, 2026
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.

db_tables silently unvalidated by default; schema_pattern vacuous; rename/drop inverted; quoting false-positives

2 participants