Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
286
.claude/skills/axiom-audit-database-schema/SKILL.md
Normal file
286
.claude/skills/axiom-audit-database-schema/SKILL.md
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: axiom-audit-database-schema
|
||||
description: Use when the user mentions database schema review, migration safety, GRDB migration audit, or SQLite schema checking.
|
||||
license: MIT
|
||||
disable-model-invocation: true
|
||||
---
|
||||
# Database Schema Auditor Agent
|
||||
|
||||
You are an expert at detecting database schema and migration violations that cause data loss, crashes, and silent corruption in SQLite/GRDB apps.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Run a comprehensive database schema audit and report all issues with:
|
||||
- File:line references for easy fixing
|
||||
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
|
||||
- Specific violation types
|
||||
- Fix recommendations with code examples
|
||||
|
||||
## Files to Exclude
|
||||
|
||||
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
|
||||
|
||||
## Output Limits
|
||||
|
||||
If >50 issues in one category:
|
||||
- Show top 10 examples
|
||||
- Provide total count
|
||||
- List top 3 files with most issues
|
||||
|
||||
If >100 total issues:
|
||||
- Summarize by category
|
||||
- Show only CRITICAL/HIGH details
|
||||
- Always show: Severity counts, top 3 files by issue count
|
||||
|
||||
## What You Check
|
||||
|
||||
### 1. ADD COLUMN NOT NULL Without DEFAULT (CRITICAL)
|
||||
**Pattern**: `ADD COLUMN ... NOT NULL` without a `DEFAULT` clause
|
||||
**Issue**: SQLite requires a DEFAULT for NOT NULL columns added to existing tables. Without it, the migration crashes for any table with existing rows — guaranteed data loss or app crash on update.
|
||||
**Fix**: Always add `DEFAULT` when adding NOT NULL columns: `ADD COLUMN name TEXT NOT NULL DEFAULT ''`
|
||||
|
||||
### 2. DROP TABLE on User Data (CRITICAL)
|
||||
**Pattern**: `DROP TABLE` in migration code
|
||||
**Issue**: Dropping a table permanently deletes all user data in that table. There is no undo.
|
||||
**Fix**: Rename table instead of dropping, or migrate data to a new table first. If intentional, add a comment explaining why.
|
||||
|
||||
### 3. DROP COLUMN (SQLite Unsupported Before 3.35.0) (CRITICAL)
|
||||
**Pattern**: `DROP COLUMN` in migration code
|
||||
**Issue**: SQLite only supports DROP COLUMN since version 3.35.0 (iOS 16+). On older iOS versions, this crashes the migration. Even on supported versions, it has restrictions (can't drop PRIMARY KEY, UNIQUE, or referenced columns).
|
||||
**Fix**: Use the 12-step table recreation pattern: create new table, copy data, drop old, rename new
|
||||
|
||||
### 4. ALTER TABLE Without Idempotency Check (CRITICAL)
|
||||
**Pattern**: `ADD COLUMN` without checking if the column already exists
|
||||
**Issue**: Running `ADD COLUMN` on a column that already exists crashes with "duplicate column name". Users who already ran this migration (e.g., beta testers) will crash on re-run.
|
||||
**Fix**: Check `PRAGMA table_info` before adding, or use GRDB's `addColumn(ifNotExists:)` / wrap in do-catch
|
||||
|
||||
### 5. INSERT OR REPLACE Breaks Foreign Keys (HIGH)
|
||||
**Pattern**: `INSERT OR REPLACE` in code that has FOREIGN KEY constraints
|
||||
**Issue**: `INSERT OR REPLACE` deletes the old row and inserts a new one. This triggers ON DELETE CASCADE, silently deleting child records. Use `INSERT ... ON CONFLICT DO UPDATE` (UPSERT) instead.
|
||||
**Fix**: Replace with `INSERT ... ON CONFLICT(id) DO UPDATE SET ...`
|
||||
|
||||
### 6. Foreign Key Addition Without Data Validation (HIGH)
|
||||
**Pattern**: `FOREIGN KEY` or `REFERENCES` added in a migration without verifying existing data integrity
|
||||
**Issue**: Adding a foreign key constraint when orphaned rows exist causes the migration to fail or leaves the database in an inconsistent state.
|
||||
**Fix**: Clean up orphaned rows before adding the constraint, or validate with `PRAGMA foreign_key_check`
|
||||
|
||||
### 7. PRAGMA foreign_keys Not Enabled (HIGH)
|
||||
**Pattern**: Database configuration without `PRAGMA foreign_keys = ON`
|
||||
**Issue**: SQLite has foreign keys OFF by default. Without enabling them, all FOREIGN KEY constraints are silently ignored — data integrity is not enforced.
|
||||
**Fix**: Enable in GRDB: `configuration.prepareDatabase { db in try db.execute(sql: "PRAGMA foreign_keys = ON") }`
|
||||
|
||||
### 8. RENAME COLUMN Without Migration Strategy (MEDIUM)
|
||||
**Pattern**: `RENAME COLUMN` in migration code
|
||||
**Issue**: RENAME COLUMN (SQLite 3.25.0+, iOS 12+) works but doesn't update application code references. Any Swift code using the old column name via raw SQL will silently break.
|
||||
**Fix**: Update all raw SQL references to the old column name. Search the codebase for the old name.
|
||||
|
||||
### 9. Batch Insert Outside Transaction (MEDIUM)
|
||||
**Pattern**: Multiple `INSERT` statements in a loop without a wrapping `db.write` / `db.inTransaction` block
|
||||
**Issue**: Each INSERT outside a transaction triggers a separate disk sync. 1000 inserts = 1000 disk syncs = 30 seconds instead of < 1 second.
|
||||
**Fix**: Wrap batch inserts in a single transaction: `try db.write { db in for item in items { try item.insert(db) } }`
|
||||
|
||||
### 10. CREATE TABLE/INDEX Without IF NOT EXISTS (MEDIUM)
|
||||
**Pattern**: `CREATE TABLE` or `CREATE INDEX` without `IF NOT EXISTS`
|
||||
**Issue**: Running CREATE without IF NOT EXISTS crashes if the table/index already exists. This breaks migration idempotency.
|
||||
**Fix**: Always use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS`
|
||||
|
||||
## Audit Process
|
||||
|
||||
### Step 1: Find All Database Files
|
||||
|
||||
Use Glob to find Swift files, then Grep to find files containing:
|
||||
- `import GRDB`
|
||||
- `DatabaseMigrator`
|
||||
- `registerMigration`
|
||||
- `ALTER TABLE`
|
||||
- `CREATE TABLE`
|
||||
- `DatabasePool`
|
||||
- `DatabaseQueue`
|
||||
- Raw SQL strings
|
||||
|
||||
### Step 2: Search for Violations
|
||||
|
||||
**Pattern 1: ADD COLUMN NOT NULL without DEFAULT**:
|
||||
```
|
||||
Grep: ADD\s+COLUMN.*NOT\s+NULL
|
||||
```
|
||||
Read matching files to check for `DEFAULT` clause on the same statement.
|
||||
|
||||
**Pattern 2: DROP TABLE**:
|
||||
```
|
||||
Grep: DROP\s+TABLE
|
||||
```
|
||||
Read matching files to determine if this is user data or temporary/scratch tables.
|
||||
|
||||
**Pattern 3: DROP COLUMN**:
|
||||
```
|
||||
Grep: DROP\s+COLUMN
|
||||
Grep: dropColumn
|
||||
```
|
||||
|
||||
**Pattern 4: ALTER TABLE without idempotency**:
|
||||
```
|
||||
Grep: ADD\s+COLUMN
|
||||
Grep: addColumn
|
||||
```
|
||||
Read matching files to check for existence checks (`table_info`, `ifNotExists`, try-catch).
|
||||
|
||||
**Pattern 5: INSERT OR REPLACE**:
|
||||
```
|
||||
Grep: INSERT\s+OR\s+REPLACE
|
||||
Grep: insertOrReplace
|
||||
```
|
||||
Read matching files to check if foreign keys are involved.
|
||||
|
||||
**Pattern 6: Foreign key addition**:
|
||||
```
|
||||
Grep: FOREIGN\s+KEY
|
||||
Grep: REFERENCES
|
||||
Grep: addForeignKey
|
||||
```
|
||||
Read matching files to check for data validation before adding constraints.
|
||||
|
||||
**Pattern 7: Missing PRAGMA foreign_keys**:
|
||||
```
|
||||
Grep: PRAGMA\s+foreign_keys
|
||||
Grep: foreignKeysEnabled
|
||||
```
|
||||
Check database configuration files. If no PRAGMA found but FOREIGN KEY constraints exist, flag it.
|
||||
|
||||
**Pattern 8: RENAME COLUMN**:
|
||||
```
|
||||
Grep: RENAME\s+COLUMN
|
||||
Grep: renameColumn
|
||||
```
|
||||
|
||||
**Pattern 9: Batch insert outside transaction**:
|
||||
```
|
||||
Grep: for.*insert\(db\)
|
||||
Grep: for.*execute.*INSERT
|
||||
```
|
||||
Read matching files to check if they're wrapped in `db.write` or `db.inTransaction`.
|
||||
|
||||
**Pattern 10: CREATE without IF NOT EXISTS**:
|
||||
```
|
||||
Grep: CREATE\s+TABLE\s+(?!IF)
|
||||
Grep: CREATE\s+INDEX\s+(?!IF)
|
||||
Grep: CREATE\s+UNIQUE\s+INDEX\s+(?!IF)
|
||||
```
|
||||
Flag CREATE statements missing IF NOT EXISTS.
|
||||
|
||||
### Step 3: Categorize by Severity
|
||||
|
||||
**CRITICAL** (Data loss or guaranteed crash):
|
||||
- ADD COLUMN NOT NULL without DEFAULT
|
||||
- DROP TABLE on user data
|
||||
- DROP COLUMN (unsupported or restricted)
|
||||
- ALTER TABLE without idempotency
|
||||
|
||||
**HIGH** (Silent data corruption or integrity failure):
|
||||
- INSERT OR REPLACE breaking foreign keys
|
||||
- Foreign key addition without data validation
|
||||
- PRAGMA foreign_keys not enabled
|
||||
|
||||
**MEDIUM** (Performance or maintainability):
|
||||
- RENAME COLUMN without code update strategy
|
||||
- Batch insert outside transaction
|
||||
- CREATE without IF NOT EXISTS
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Database Schema Audit Results
|
||||
|
||||
## Summary
|
||||
- **CRITICAL Issues**: [count] (Data loss/crash risk)
|
||||
- **HIGH Issues**: [count] (Silent corruption/integrity risk)
|
||||
- **MEDIUM Issues**: [count] (Performance/maintainability)
|
||||
|
||||
## Risk Score: [0-10]
|
||||
(Each CRITICAL = +3 points, HIGH = +2 points, MEDIUM = +1 point, cap at 10)
|
||||
|
||||
## CRITICAL Issues
|
||||
|
||||
### ADD COLUMN NOT NULL Without DEFAULT
|
||||
- `Migrations.swift:78` - `ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL`
|
||||
- **Risk**: Migration crashes for all users with existing data
|
||||
- **Fix**:
|
||||
```swift
|
||||
// WRONG — crashes if table has rows
|
||||
try db.execute(sql: "ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL")
|
||||
|
||||
// CORRECT — safe for existing rows
|
||||
try db.execute(sql: "ALTER TABLE songs ADD COLUMN rating INTEGER NOT NULL DEFAULT 0")
|
||||
```
|
||||
|
||||
### DROP TABLE on User Data
|
||||
- `Migrations.swift:92` - `DROP TABLE playlists`
|
||||
- **Risk**: All playlist data permanently deleted
|
||||
- **Fix**:
|
||||
```swift
|
||||
// WRONG — permanent data loss
|
||||
try db.execute(sql: "DROP TABLE playlists")
|
||||
|
||||
// CORRECT — preserve data
|
||||
try db.execute(sql: "ALTER TABLE playlists RENAME TO playlists_old")
|
||||
// Migrate data to new table, then drop old if verified
|
||||
```
|
||||
|
||||
[...continue for each issue found...]
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Fix CRITICAL issues immediately** - Migration will crash in production
|
||||
2. **Enable foreign keys** if using FK constraints
|
||||
3. **Test migrations on real device** with production-size data
|
||||
4. **Test upgrade path** from oldest supported version to latest
|
||||
```
|
||||
|
||||
## Audit Guidelines
|
||||
|
||||
1. Run all 10 pattern searches for comprehensive coverage
|
||||
2. Provide file:line references to make issues easy to locate
|
||||
3. Show exact fixes with code examples for each issue
|
||||
4. Categorize by severity to help prioritize fixes
|
||||
5. Calculate risk score to quantify overall safety level
|
||||
|
||||
## When Issues Found
|
||||
|
||||
If CRITICAL issues found:
|
||||
- Emphasize data loss risk for all existing users
|
||||
- Recommend fixing before any App Store submission
|
||||
- Provide explicit SQL fixes
|
||||
- Calculate time to fix (usually 5-10 minutes per issue)
|
||||
|
||||
If NO issues found:
|
||||
- Report "No database schema violations detected"
|
||||
- Note that migration testing on real data is still recommended
|
||||
- Suggest testing upgrade from oldest supported app version
|
||||
|
||||
## False Positives (Not Issues)
|
||||
|
||||
- `DROP TABLE` on temporary or scratch tables (not user data)
|
||||
- `DROP TABLE` behind `#if DEBUG` flag
|
||||
- `ADD COLUMN` with `try?` or wrapped in do-catch (implicit idempotency)
|
||||
- `INSERT OR REPLACE` on tables without foreign key constraints
|
||||
- `CREATE TABLE` inside `registerMigration` (runs once by design, but IF NOT EXISTS still recommended)
|
||||
- Batch inserts of < 10 items (transaction overhead not worth it)
|
||||
|
||||
## Risk Score Calculation
|
||||
|
||||
- Each CRITICAL issue: +3 points
|
||||
- Each HIGH issue: +2 points
|
||||
- Each MEDIUM issue: +1 point
|
||||
- Maximum score: 10
|
||||
|
||||
**Interpretation**:
|
||||
- 0-2: Low risk, migrations safe
|
||||
- 3-5: Medium risk, review before release
|
||||
- 6-8: High risk, data loss likely
|
||||
- 9-10: Critical risk, do not ship
|
||||
|
||||
## Related
|
||||
|
||||
For database migration patterns: `axiom-database-migration` skill
|
||||
For GRDB patterns: `axiom-grdb` skill
|
||||
For SwiftData migrations: `axiom-swiftdata-migration` skill
|
||||
Reference in New Issue
Block a user