Implements tag/category system backend following microformats2 p-category specification. Database changes: - Migration 008: Add tags and note_tags tables - Normalized tag storage (case-insensitive lookup, display name preserved) - Indexes for performance New module: - starpunk/tags.py: Tag management functions - normalize_tag: Normalize tag strings - get_or_create_tag: Get or create tag records - add_tags_to_note: Associate tags with notes (replaces existing) - get_note_tags: Retrieve note tags (alphabetically ordered) - get_tag_by_name: Lookup tag by normalized name - get_notes_by_tag: Get all notes with specific tag - parse_tag_input: Parse comma-separated tag input Model updates: - Note.tags property (lazy-loaded, prefer pre-loading in routes) - Note.to_dict() add include_tags parameter CRUD updates: - create_note() accepts tags parameter - update_note() accepts tags parameter (None = no change, [] = remove all) Micropub integration: - Pass tags to create_note() (tags already extracted by extract_tags()) - Return tags in q=source response Per design doc: docs/design/v1.3.0/microformats-tags-design.md Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
191 lines
6.6 KiB
Markdown
191 lines
6.6 KiB
Markdown
# Database Migration Conflict Diagnosis Report
|
|
|
|
## Executive Summary
|
|
The v1.0.0-rc.2 container is failing because migration 002 attempts to CREATE TABLE authorization_codes, but this table already exists in the production database (created by v1.0.0-rc.1's SCHEMA_SQL).
|
|
|
|
## Issue Details
|
|
|
|
### Error Message
|
|
```
|
|
Migration 002_secure_tokens_and_authorization_codes.sql failed: table authorization_codes already exists
|
|
```
|
|
|
|
### Root Cause
|
|
**Conflicting Database Initialization Strategies**
|
|
|
|
1. **SCHEMA_SQL in database.py (lines 58-76)**: Creates the `authorization_codes` table directly
|
|
2. **Migration 002 (line 33)**: Also attempts to CREATE TABLE authorization_codes
|
|
|
|
The production database was initialized with v1.0.0-rc.1's SCHEMA_SQL, which created the table. When v1.0.0-rc.2 runs, migration 002 fails because the table already exists.
|
|
|
|
## Database State Analysis
|
|
|
|
### What v1.0.0-rc.1 Created (via SCHEMA_SQL)
|
|
```sql
|
|
-- From database.py lines 58-76
|
|
CREATE TABLE IF NOT EXISTS authorization_codes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code_hash TEXT UNIQUE NOT NULL,
|
|
me TEXT NOT NULL,
|
|
client_id TEXT NOT NULL,
|
|
redirect_uri TEXT NOT NULL,
|
|
scope TEXT,
|
|
state TEXT,
|
|
code_challenge TEXT,
|
|
code_challenge_method TEXT,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
used_at TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_auth_codes_hash ON authorization_codes(code_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON authorization_codes(expires_at);
|
|
```
|
|
|
|
### What Migration 002 Tries to Do
|
|
```sql
|
|
-- From migration 002 lines 33-46
|
|
CREATE TABLE authorization_codes ( -- NO "IF NOT EXISTS" clause!
|
|
-- Same structure as above
|
|
);
|
|
```
|
|
|
|
The migration uses CREATE TABLE without IF NOT EXISTS, causing it to fail when the table already exists.
|
|
|
|
## The Good News: System Already Has the Solution
|
|
|
|
The migrations.py file has sophisticated logic to handle this exact scenario:
|
|
|
|
### Detection Logic (migrations.py lines 176-211)
|
|
```python
|
|
def is_migration_needed(conn, migration_name):
|
|
if migration_name == "002_secure_tokens_and_authorization_codes.sql":
|
|
# Check if tables exist
|
|
if not table_exists(conn, 'authorization_codes'):
|
|
return True # Run full migration
|
|
if not column_exists(conn, 'tokens', 'token_hash'):
|
|
return True # Run full migration
|
|
|
|
# Check if indexes exist
|
|
has_all_indexes = (
|
|
index_exists(conn, 'idx_tokens_hash') and
|
|
index_exists(conn, 'idx_tokens_me') and
|
|
# ... other index checks
|
|
)
|
|
|
|
if not has_all_indexes:
|
|
# Tables exist but indexes missing
|
|
# Don't run full migration, handle separately
|
|
return False
|
|
```
|
|
|
|
### Resolution Logic (migrations.py lines 383-410)
|
|
When tables exist but indexes are missing:
|
|
```python
|
|
if migration_name == "002_secure_tokens_and_authorization_codes.sql":
|
|
# Create only missing indexes
|
|
indexes_to_create = []
|
|
if not index_exists(conn, 'idx_tokens_hash'):
|
|
indexes_to_create.append("CREATE INDEX idx_tokens_hash ON tokens(token_hash)")
|
|
# ... check and create other indexes
|
|
|
|
# Apply indexes without running full migration
|
|
for index_sql in indexes_to_create:
|
|
conn.execute(index_sql)
|
|
|
|
# Mark migration as applied
|
|
conn.execute(
|
|
"INSERT INTO schema_migrations (migration_name) VALUES (?)",
|
|
(migration_name,)
|
|
)
|
|
```
|
|
|
|
## Why Is It Still Failing?
|
|
|
|
The error suggests the smart detection logic isn't being triggered. Possible reasons:
|
|
|
|
1. **Migration Already Marked as Applied**: Check if schema_migrations table already has migration 002 listed
|
|
2. **Different Code Path**: The production container might not be using the smart detection path
|
|
3. **Transaction Rollback**: An earlier error might have left the database in an inconsistent state
|
|
|
|
## Immediate Solution
|
|
|
|
### Option 1: Verify Smart Detection Is Working
|
|
The system SHOULD handle this automatically. If it's not, check:
|
|
1. Is migrations.py line 378 being reached? (migration_count == 0 check)
|
|
2. Is is_migration_needed() being called for migration 002?
|
|
3. Are the table existence checks working correctly?
|
|
|
|
### Option 2: Manual Database Fix (if smart detection fails)
|
|
```sql
|
|
-- Check current state
|
|
SELECT * FROM schema_migrations WHERE migration_name LIKE '%002%';
|
|
|
|
-- If migration 002 is NOT listed, mark it as applied
|
|
INSERT INTO schema_migrations (migration_name)
|
|
VALUES ('002_secure_tokens_and_authorization_codes.sql');
|
|
|
|
-- Ensure indexes exist (if missing)
|
|
CREATE INDEX IF NOT EXISTS idx_tokens_hash ON tokens(token_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_tokens_me ON tokens(me);
|
|
CREATE INDEX IF NOT EXISTS idx_tokens_expires ON tokens(expires_at);
|
|
CREATE INDEX IF NOT EXISTS idx_auth_codes_hash ON authorization_codes(code_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_auth_codes_expires ON authorization_codes(expires_at);
|
|
```
|
|
|
|
## Long-term Architecture Fix
|
|
|
|
### Current Issue
|
|
SCHEMA_SQL and migrations have overlapping responsibilities:
|
|
- SCHEMA_SQL creates authorization_codes table (v1.0.0-rc.1+)
|
|
- Migration 002 also creates authorization_codes table
|
|
|
|
### Recommended Solution
|
|
**Already Implemented!** The smart detection in migrations.py handles this correctly.
|
|
|
|
### Why It Should Work
|
|
1. When database has tables from SCHEMA_SQL but no migration records:
|
|
- is_migration_needed() detects tables exist
|
|
- Returns False to skip full migration
|
|
- Creates only missing indexes
|
|
- Marks migration as applied
|
|
|
|
2. The system is designed to be self-healing and handle partial schemas
|
|
|
|
## Verification Steps
|
|
|
|
1. **Check Migration Status**:
|
|
```sql
|
|
SELECT * FROM schema_migrations;
|
|
```
|
|
|
|
2. **Check Table Existence**:
|
|
```sql
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name='authorization_codes';
|
|
```
|
|
|
|
3. **Check Index Existence**:
|
|
```sql
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='index' AND name LIKE 'idx_%';
|
|
```
|
|
|
|
4. **Check Schema Version Detection**:
|
|
- The is_schema_current() function should return False (missing indexes)
|
|
- This should trigger the smart migration path
|
|
|
|
## Conclusion
|
|
|
|
The architecture already has the correct solution implemented in migrations.py. The smart detection logic should:
|
|
1. Detect that authorization_codes table exists
|
|
2. Skip the table creation
|
|
3. Create only missing indexes
|
|
4. Mark migration 002 as applied
|
|
|
|
If this isn't working, the issue is likely:
|
|
- A bug in the detection logic execution path
|
|
- The production database already has migration 002 marked as applied (check schema_migrations)
|
|
- A transaction rollback leaving the database in an inconsistent state
|
|
|
|
The system is designed to handle this exact scenario. If it's failing, we need to debug why the smart detection isn't being triggered. |