Fix: schema-discovery fail-open (unreadable migration silently disabled DB validation)#326
Fix: schema-discovery fail-open (unreadable migration silently disabled DB validation)#3260xLeif wants to merge 1 commit into
Conversation
…ntly disables DB validation Audit Batch 4 — the schema half of the file-read-fail-loud theme (split from #325). build_schema silently skips a schema/migration file it can't read as UTF-8 (`Err(_) => continue`). If every schema file is unreadable, the discovered table set is empty, and validator.rs's `!schema_tables.is_empty()` guard then skips the `db_tables` (and column) checks entirely — so a corrupted migration made DB validation a silent no-op, passing green even under `check --strict`. Fix: add `schema::schema_read_errors(schema_dir)`, which returns an error for each schema file that EXISTS but fails a UTF-8 read (scanning the same SQL_EXTENSIONS files build_schema would). Wire it once, project-level, into the shared `run_validation` (commands/mod.rs) after the per-spec loop, so it gates every validating command (check, coverage, generate, comment, issues) at a single point without churning the 8+ get_schema_table_names / build_schema_columns call sites. An unreadable migration now counts as a hard error: printed in the report and gating the exit under strict/enforcement. `UnknownLanguage`-style non-schema files and a genuinely-empty-but-readable schema stay benign; an absent schema_dir (schema simply not configured) produces nothing. Reproduced: with schema_dir set and a non-UTF-8 001_init.sql, `check` went from silent exit 0 (no error at all, even --strict) to a surfaced "could not be read as UTF-8; DB schema validation would be incomplete" error that gates the strict exit. Readable migration and no-schema-configured both produce zero schema errors. Tests: schema_read_errors unit test (absent dir / readable / non-schema file / non-UTF-8) + 2 integration (check gates on unreadable migration; no false positive on readable). Documented as schema spec invariant #2a + API row. 751 unit + 179 integration, fmt / clippy / self-check 100%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to detect and report schema or migration files that exist but cannot be read as UTF-8, preventing silent validation bypasses. It adds the schema_read_errors function to scan for these files and surfaces them as hard errors during validation. The review feedback suggests improving the robustness of schema_read_errors by explicitly handling potential errors from fs::read_dir and optimizing the file extension filtering to avoid unnecessary string allocations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| let mut files: Vec<_> = fs::read_dir(schema_dir) | ||
| .into_iter() | ||
| .flatten() | ||
| .flatten() | ||
| .filter(|e| { | ||
| let ext = e | ||
| .path() | ||
| .extension() | ||
| .and_then(|x| x.to_str()) | ||
| .unwrap_or("") | ||
| .to_string(); | ||
| SQL_EXTENSIONS.contains(&ext.as_str()) | ||
| }) | ||
| .collect(); |
There was a problem hiding this comment.
Correctness & Efficiency Improvement
- Error Handling: If
schema_direxists but is unreadable (e.g., due to permission issues or if it is actually a file),fs::read_dirwill return anErr. The current implementation silently ignores this error using.into_iter().flatten().flatten(), which can lead to a silent fail-open (the exact issue this PR aims to prevent). - Performance: The current filter allocates a new
Stringfor every single file in the directory by calling.to_string(). We can avoid this allocation entirely by matching directly on the&strextension.
We can address both issues by explicitly handling the fs::read_dir result and optimizing the extension filter.
let read_dir = match fs::read_dir(schema_dir) {
Ok(rd) => rd,
Err(err) => {
errors.push(format!(
"Could not read schema directory `{}`: {err}",
schema_dir.display()
));
return errors;
}
};
let mut files: Vec<_> = read_dir
.flatten()
.filter(|e| {
e.path()
.extension()
.and_then(|x| x.to_str())
.map(|ext| SQL_EXTENSIONS.contains(&ext))
.unwrap_or(false)
})
.collect();There was a problem hiding this comment.
✅ Corvin says...
_
<(^\ .oO(Caw! ^v^)
|/(\
\(\\
" "\\
"Looking sharp! Like a beak should be."
CI Summary
| Check | Status |
|---|---|
| Validate action.yml | ✅ Passed |
| Dependency Audit | ✅ Passed |
| Code Coverage | ✅ Passed |
| Format Check | ✅ Passed |
| Docs Site | ✅ Passed |
| Spec Validation | ✅ Passed |
| Tests (build, test, clippy) | ✅ Passed |
| VS Code Extension | ✅ Passed |
📋 Spec Validation Details
✅ SpecSync: Passed
| Metric | Value |
|---|---|
| Specs checked | 60 |
| Passed | 60 |
| Errors | 0 |
| Warnings | 0 |
| File coverage | 100% (76/76) |
| LOC coverage | 100% (38524/38524) |
Generated by specsync · Run specsync check --format github to reproduce
Powered by corvid-pet
|
Adversarial review: READY_WITH_NITS, 93 — no correctness defects.
Non-blocking nits (noted, not fixed here):
|
Kyntrin
left a comment
There was a problem hiding this comment.
Thanks for splitting out the schema half. The core direction is right, and CI is green, but I found two blockers before merge.
Requested changes:
-
The existing unresolved inline comment on
src/schema.rsis valid:schema_read_errorsstill usesfs::read_dir(schema_dir).into_iter().flatten().flatten(). Ifschema_direxists but cannot be read, or is a file rather than a directory,read_dirreturnsErrand this function returns no errors. That is another fail-open path for the same validation surface: schema discovery becomes empty anddb_tables/ column checks can be skipped without signal. Please handle theread_direrror explicitly and return a hard schema-read error. -
specs/schema/schema.spec.mdadds a new exported function and invariant, but the spec frontmatter version is still unchanged and the Change Log has no entry. Per the repo workflow, spec contract changes need a version bump and dated Change Log entry.
Non-blocking note: the extension filter can avoid allocating a String for every directory entry by matching on the &str extension directly, as Gemini suggested. That part is a small cleanup; the read_dir error handling is the blocking correctness issue.
Audit Batch 4 — the schema half of the file-read-fail-loud theme (split out of #325 to keep that PR focused).
Root cause
build_schemasilently skips a schema/migration file it can't read as UTF-8 (Err(_) => continue). If every schema file is unreadable, the discovered table set is empty, andvalidator.rs's!schema_tables.is_empty()guard then skips thedb_tablesand column checks entirely — so a corrupted migration made DB validation a silent no-op, passing green even undercheck --strict.Fix
Add
schema::schema_read_errors(schema_dir)— an error per schema file that exists but fails a UTF-8 read (scanning the sameSQL_EXTENSIONSfilesbuild_schemawould). Wire it once, project-level, into the sharedrun_validationafter the per-spec loop, so it gates every validating command (check, coverage, generate, comment, issues) at a single point — no churn across the 8+get_schema_table_names/build_schema_columnscall sites. An unreadable migration is now a hard error: printed in the report and gating the exit under strict/enforcement.Benign cases preserved: a genuinely-empty-but-readable schema, non-schema files, and an absent
schema_dir(schema simply not configured) all produce nothing.Reproduced
With
schema_dirset and a non-UTF-8001_init.sql:checkwent from silent exit 0 (no error at all, even--strict) to a surfacedcould not be read as UTF-8; DB schema validation would be incompleteerror that gates the strict exit. Readable migration and no-schema-configured → zero schema errors.Tests
schema_read_errorsunit test (absent dir / readable / non-schema file / non-UTF-8) + 2 integration (gates on unreadable migration; no false positive on readable). Documented as schema spec invariant #2a + API row. 751 unit + 179 integration, fmt / clippy / self-check 100%.Part of the post-v4.7.1 audit sweep (Batch 4). Branches off the pre-#325 main; independent files (schema.rs, commands/mod.rs) — merges cleanly alongside #325.
🤖 Generated with Claude Code