The prior user-deletion work updated the PG schema and a live-PG migration
but left the canonical schema definitions inconsistent, breaking user
deletion on fresh PG installs and on all SQLite dev installs.
- schema.sql: add ON DELETE SET NULL to context_files.updated_by (was the
only user FK missing it; fresh PG installs could not delete an authoring
user).
- schema_sqlite.sql: bring five user_id FK columns into lockstep with PG
(drop NOT NULL, add ON DELETE SET NULL): project_context.updated_by,
context_files.updated_by, change_requests.submitted_by,
reviews.reviewer_id, audit_log.user_id.
- schema_sqlite.sql: remove the audit_log append-only UPDATE/DELETE triggers.
ON DELETE SET NULL on audit_log.user_id is an UPDATE the trigger aborted,
so deleting any user who had ever logged in failed. This mirrors schema.sql,
which dropped the equivalent PG triggers in fc1a2f5; append-only is enforced
at the application layer (db.py only INSERTs into audit_log).
- db.py: user_delete no longer swallows non-FK exceptions on the SQLite path
(Exception masked sqlite3.IntegrityError); only FK violations map to the
soft "user_has_references" response, everything else propagates. PG
rollback-on-any-error (shared-connection cascade fix) is preserved.
- db.py: document that SQLite cannot ALTER FK constraints in place; existing
dev DBs must be recreated to pick up these changes.
- server.py: the global 409 handler no longer leaks raw psycopg text (index
names, column expressions) to API callers; it is logged instead.
- migrate_user_fk_set_null.py: use the column from FKS_TO_FIX directly instead
of re-deriving it from the constraint name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add CLAUDE.md (Claude Code orientation for the repo).
- Remove app/src/ctxd.egg-info/* from version control and gitignore
*.egg-info/ — it is regenerated by `pip install -e` and only dirties
the working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename code.cubecraftcreations.com -> code.cubecraftlabs.com in the
deployment guide and article links.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The REST/Web-UI HTTPServer shares one long-lived PG connection across all
requests. Any statement that raised mid-request (e.g. a UniqueViolation from
a desynced SERIAL sequence) aborted the transaction; the global handler
returned 500 without rolling back, so every subsequent request failed with
InFailedSqlTransaction until restart — surfacing as "duplicate keys cause
500s" and "500 immediately after login".
- server.py: global handler now always rolls back the shared connection on
error and maps constraint violations to 409 (was 500/400). This is the one
funnel that guarantees the connection is never left aborted.
- db.py: add is_integrity_error() — dual-backend (psycopg + sqlite3)
constraint-violation classifier; replaces the fragile `"UNIQUE" in msg`
string match that never matched Postgres' error text.
- Remove make_write_mcp_server: a never-run duplicate of the MCP write tools
that had bit-rotted (wrong file_update arg order + FK-violating hardcoded
actor). Live writes go through oauth_mcp_app, which is correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
audit_log.user_id, change_requests.submitted_by, reviews.reviewer_id
were NOT NULL but had ON DELETE SET NULL — PG can't set a NOT NULL
column to NULL on delete, so user deletion failed. Migration now drops
NOT NULL before altering the FK constraint.
- schema.sql: all user_id FKs now ON DELETE SET NULL (was RESTRICT)
- migrate_user_fk_set_null.py: drop+readd constraints on existing DBs
- entrypoint.sh: runs migration automatically on startup (non-fatal)
- db.py: rollback moved before FK check (cleanup)
Deleting a user now nullifies their references in audit_log,
project_context, context_files, change_requests, reviews instead of
blocking with a 409.
PostgreSQL enters 'current transaction is aborted' state after a
ForeignKeyViolation. Without rollback(), every subsequent query on the
shared connection fails with 'commands ignored until end of transaction
block'. Now both db.py and server.py rollback on the error path.
- New actions column (right of state) with inline buttons per row
- Manage form keeps save user + clear only (no duplicate action buttons)
- Themed to match admin dialog: JetBrains Mono, uppercase, square borders
- Delete uses danger styling
- OAuth read+write on /mcp; API key on /mcp still full internal tools (LAN)
- /readonly/mcp and /oauth/mcp remain OAuth aliases
- OAuth metadata and connector_url point to /mcp
- README + Traefik template: route Host without blocking /mcp
- Route public write at GET /write/sse and POST /write/messages (ctxd.write)
- Always require write token on /write/messages (was optional)
- Remove debug tracing; document SSE write surface in SKILL.md
- Add scripts/test_write_mcp.py for local OAuth write smoke test
Migration script now resets SERIAL sequences (audit_log, context_files, etc.)
to max(existing_id) after copying rows. Without this, the sequence is still at
its post-schema-creation value and every INSERT hits a duplicate key error.
- entrypoint.sh: use NEEDS_INIT variable instead of exit codes (set -e was killing the script)
- __main__.py: skip db_path.exists() check when using PostgreSQL
- cli.py: use dual-backend placeholders for _seed_context (was using SQLite ? syntax)
- cli.py: use 'admin' instead of 'system' for seed updated_by (FK constraint)