mirror of
https://github.com/zvx-echo6/central.git
synced 2026-08-26 17:31:39 +00:00
Adds a `kind` TEXT column to `config.adapters` (migration 043) so one adapter
class can later back many operator-created instances. Every built-in adapter
row is back-filled with `kind = name`, keeping all 23 adapters working
unchanged.
- Migration 043: ADD COLUMN IF NOT EXISTS kind, back-fill, SET NOT NULL.
- AdapterConfig gains `kind: str | None` with a model_validator that falls
back to `name` when the column is NULL/absent (pre-043 rows).
- config_store.get_adapter / list_adapters: add `kind` to SELECT.
- supervisor._create_adapter AND _start_adapter (api-key precondition):
resolve the class by config.kind (class key) not config.name (instance key);
runtime state keying stays on config.name.
- routes.py GET/POST /adapters/{name} and adapters_list: add `kind` to SELECT,
resolve the adapter class by row["kind"].
- adapter_discovery.py: comment clarifying .name is the kind (class identity).
- Out of scope, left as-is (built-ins only, name==kind): setup wizard paths in
routes.py (~750/~891/~1109) and gui/__init__.py.
- Tests: 17 new pure-unit tests (migration SQL shape, AdapterConfig back-compat,
_create_adapter + _start_adapter kind dispatch). Existing test mock rows
updated to include the `kind` field.
Co-authored-by: Ubuntu <zvx@cortex.echo6.co>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
48 lines
1.7 KiB
Python
48 lines
1.7 KiB
Python
"""v0.15.0 migration 043: add kind column to config.adapters.
|
|
|
|
No live Postgres required — asserts the migration SQL is structurally correct:
|
|
adds the column idempotently, back-fills kind = name for pre-existing rows, and
|
|
enforces NOT NULL going forward.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
_SQL = Path("sql/migrations/043_add_adapters_kind_column.sql").read_text()
|
|
_NORM = " ".join(_SQL.split()) # whitespace-insensitive matching
|
|
|
|
|
|
def test_uses_add_column_if_not_exists():
|
|
"""Must be idempotent — safe to re-run after partial application."""
|
|
assert "ADD COLUMN IF NOT EXISTS kind TEXT" in _NORM
|
|
|
|
|
|
def test_backfills_kind_equals_name_for_null_rows():
|
|
"""Existing rows must get kind = name so built-in adapters keep working."""
|
|
assert "UPDATE config.adapters SET kind = name WHERE kind IS NULL" in _NORM
|
|
|
|
|
|
def test_enforces_not_null_after_backfill():
|
|
"""kind must be NOT NULL once every row is back-filled."""
|
|
assert "ALTER COLUMN kind SET NOT NULL" in _NORM
|
|
|
|
|
|
def test_does_not_add_default_clause():
|
|
"""No DEFAULT — future INSERT paths must always supply kind explicitly."""
|
|
# The ADD COLUMN line should not contain DEFAULT
|
|
add_line = next(
|
|
(line for line in _SQL.splitlines() if "ADD COLUMN" in line), ""
|
|
)
|
|
assert "DEFAULT" not in add_line.upper()
|
|
|
|
|
|
def test_does_not_drop_any_columns():
|
|
"""Pure additive migration — must not drop anything."""
|
|
upper = _NORM.upper()
|
|
assert "DROP COLUMN" not in upper
|
|
assert "DROP TABLE" not in upper
|
|
|
|
|
|
def test_explains_name_vs_kind_split_in_comment():
|
|
"""Header comment must explain the instance-vs-class identity split."""
|
|
assert "instance" in _SQL.lower()
|
|
assert "class" in _SQL.lower()
|