# Adapter Foundations Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Route all DWH and vector operations through stable typed contracts while preserving current direct/REST behavior. **Architecture:** Define small Python protocols and capability records, implement adapters around existing modules, and centralize construction in one factory. Introduce a discriminated workspace schema with a compatibility translator for current YAML. **Tech Stack:** Python 3.11+, `typing.Protocol`, Pydantic 2, SQLAlchemy, psycopg2, requests, pytest, Typer. ## Global Constraints - Do not change workflow phases or CLI output formats. - PostgreSQL direct and current Thoth/PostgREST are the only supported DWH transports in this plan. - Preserve reader/writer vector credential separation. - All migrations must accept existing `database.transport`, `rest`, `vector_db`, `vector_rest`, and `vector_write_rest` fields. - JSON stdout stays pristine; diagnostics go to stderr unless part of the JSON result. --- ### Task 1: Define DWH ports and capabilities **Files:** - Create: `harness/tht/ports/__init__.py` - Create: `harness/tht/ports/dwh.py` - Test: `harness/tests/test_dwh_port_contract.py` **Interfaces:** - Produces: `DwhCapabilities`, `DwhAdapter`, `DwhHealth`, `DistinctValues`, and `UnsupportedCapability`. - Consumes: existing catalog models from `tht.db.introspect` and execution result types from `tht.db.execute`. - [ ] **Step 1: Write the failing protocol-shape test** ```python def test_fake_adapter_satisfies_runtime_protocol(): adapter = FakeDwhAdapter() assert isinstance(adapter, DwhAdapter) assert adapter.capabilities.explain is True assert adapter.health().ok is True ``` - [ ] **Step 2: Run the focused test and confirm failure** Run: `cd harness && .venv/bin/pytest tests/test_dwh_port_contract.py -q` Expected: FAIL because `tht.ports.dwh` does not exist. - [ ] **Step 3: Add the minimal public contract** ```python @dataclass(frozen=True) class DwhCapabilities: introspection: bool = True explain: bool = True sampling: bool = True distinct_values: bool = True @dataclass(frozen=True) class DistinctValues: values: list[object] truncated: bool @runtime_checkable class DwhAdapter(Protocol): @property def capabilities(self) -> DwhCapabilities: ... def health(self) -> DwhHealth: ... def introspect(self) -> PhysicalSchema: ... def run_query(self, sql: str, *, limit: int) -> ExecResult: ... def explain(self, sql: str) -> PlanSummary: ... def sample_column(self, table: str, column: str, *, limit: int) -> list[object]: ... def distinct_values(self, table: str, column: str) -> DistinctValues: ... ``` - [ ] **Step 4: Run contract test and type-oriented import smoke test** Run: `cd harness && .venv/bin/pytest tests/test_dwh_port_contract.py -q` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add harness/tht/ports harness/tests/test_dwh_port_contract.py git commit -m "refactor(dwh): define adapter contract" ``` ### Task 2: Wrap direct and REST DWH implementations **Files:** - Create: `harness/tht/adapters/dwh/__init__.py` - Create: `harness/tht/adapters/dwh/postgres.py` - Create: `harness/tht/adapters/dwh/thoth_rest.py` - Test: `harness/tests/test_dwh_adapters.py` - Test: `harness/tests/test_dwh_port_contract.py` - Test: `harness/tests/l0/test_db_sampling.py` - Modify: `harness/tht/ports/__init__.py` - Modify: `harness/tht/ports/dwh.py` - Modify: `harness/tht/execute/__init__.py` - Modify: `harness/tht/db/execute.py` - Modify: `harness/tht/db/sampling.py` - Modify: `harness/tht/rest/execute.py` - Modify: `docs/superpowers/plans/2026-07-11-adapter-foundations.md` **Interfaces:** - Consumes: `DwhAdapter` from Task 1; existing `DatabaseConfig`, `RestConfig`, catalog, sampling, execute, and explain functions. - Produces: `PostgresDwhAdapter(config)` and `ThothRestDwhAdapter(database, rest)`. - [ ] **Step 1: Add parametrized contract tests for both wrappers** ```python @pytest.mark.parametrize("factory", [postgres_factory, rest_factory]) def test_adapter_rejects_write_sql(factory): with pytest.raises(ExecutionError): factory().run_query("delete from fact_sales", limit=10) ``` - [ ] **Step 2: Verify failure** Run: `cd harness && .venv/bin/pytest tests/test_dwh_adapters.py -q` Expected: FAIL because the adapter classes are absent. - [ ] **Step 3: Implement thin wrappers, without duplicating transport logic** ```python class PostgresDwhAdapter: capabilities = DwhCapabilities() def __init__(self, config: DatabaseConfig): self._config = config self._engine = make_engine(config) def run_query(self, sql: str, *, limit: int) -> ExecResult: return run_query(self._engine, sql, limit=limit) ``` Implement the analogous REST wrapper by delegating to `tht.rest.*`; translate transport-specific errors only at the adapter boundary. Both wrappers delegate frequency-ranked, distinct sampling to the paired implementations in `tht.db.sampling`. Query and sampling limits must be runtime-positive integers (booleans and floats are rejected), and `distinct_values` reports any cap through `DistinctValues.truncated`. - [ ] **Step 4: Run adapter, read-only, sampling, and REST tests** Run: `cd harness && .venv/bin/pytest tests/test_dwh_adapters.py tests/test_readonly_guard.py tests/test_rest_client.py tests/l0/test_db_sampling.py -q` Expected: PASS; L0 may deselect when Docker is unavailable. - [ ] **Step 5: Commit** ```bash git add docs/superpowers/plans/2026-07-11-adapter-foundations.md \ harness/tht/ports harness/tht/adapters/dwh harness/tht/execute/__init__.py \ harness/tht/db/execute.py harness/tht/db/sampling.py harness/tht/rest/execute.py \ harness/tests/test_dwh_port_contract.py harness/tests/test_dwh_adapters.py \ harness/tests/l0/test_db_sampling.py git commit -m "refactor(dwh): adapt direct and REST transports" ``` ### Task 3: Define vector port and wrappers **Files:** - Create: `harness/tht/ports/vector.py` - Create: `harness/tht/adapters/vector/__init__.py` - Create: `harness/tht/adapters/vector/thoth_http.py` - Create: `harness/tht/adapters/vector/legacy_direct.py` - Test: `harness/tests/test_vector_port_contract.py` - Modify: `harness/tht/vectorstore/reader.py` **Interfaces:** - Produces: `VectorStore`, `VectorCapabilities`, `VectorHealth`, `VectorRecord`, `VectorWriteRecord`, `VectorHit`, `ThothHttpVectorStore`. - Preserves: current `VectorRestClient`, `DirectSearcher`, and `RestSearcher` behavior behind wrappers. - [ ] **Step 1: Write read/write capability and dual-credential tests** ```python def test_http_store_reports_reader_without_writer(): store = ThothHttpVectorStore(reader=reader, writer=None) assert store.capabilities.search is True assert store.capabilities.upsert is False with pytest.raises(VectorWriteUnavailable): store.upsert("memory", []) ``` - [ ] **Step 2: Verify failure** Run: `cd harness && .venv/bin/pytest tests/test_vector_port_contract.py -q` Expected: FAIL because the vector port is absent. - [ ] **Step 3: Implement the vector contract and wrappers** ```python @runtime_checkable class VectorStore(Protocol): @property def capabilities(self) -> VectorCapabilities: ... def health(self) -> VectorHealth: ... def search(self, collections: list[str], embedding: list[float], *, limit: int, kinds: list[str] | None = None) -> list[VectorHit]: ... def existing_hashes(self, collection: str, kinds: list[str]) -> dict[str, str]: ... def upsert(self, collection: str, records: list[VectorWriteRecord]) -> int: ... ``` `VectorWriteRecord` is the transport-neutral write envelope: it contains the canonical `VectorRecord`, a precomputed embedding, and a content hash. Adapters must preserve `VectorRecord.metadata` unchanged, including semantic keys named `embedding` or `content_hash`. - [ ] **Step 4: Run vector regression tests** Run: `cd harness && .venv/bin/pytest tests/test_vector_port_contract.py tests/test_vector_dual_key.py tests/test_search_similar_kinds.py tests/test_memory_save_one.py tests/test_solved_question.py -q` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add harness/tht/ports/vector.py harness/tht/adapters/vector harness/tht/vectorstore/reader.py harness/tests/test_vector_port_contract.py git commit -m "refactor(vector): define store contract" ``` ### Task 4: Introduce discriminated resource configuration with legacy translation **Files:** - Create: `harness/tht/config_compat.py` - Modify: `harness/tht/config.py` - Modify: `harness/workspaces/tht.example.yaml` - Test: `harness/tests/test_config_resources.py` - Test: `harness/tests/test_config_legacy_compat.py` **Interfaces:** - Produces: `DwhResourceConfig`, `VectorResourceConfig`, `WorkspaceRoots` and `translate_legacy_config(raw)`. - Consumes: existing YAML environment expansion and `ConfigError` behavior. - [ ] **Step 1: Write new-schema and legacy-equivalence tests** ```python def test_legacy_rest_workspace_equals_new_resource_schema(tmp_path): old = load_config(write_old_workspace(tmp_path)) new = load_config(write_new_workspace(tmp_path)) assert old.dwh.model_dump() == new.dwh.model_dump() ``` - [ ] **Step 2: Verify failure** Run: `cd harness && .venv/bin/pytest tests/test_config_resources.py tests/test_config_legacy_compat.py -q` Expected: FAIL because `dwh` and discriminated vector resources are absent. - [ ] **Step 3: Add discriminated models and an isolated translator** ```python class PostgresDwhConfig(BaseModel): type: Literal["postgres_direct"] connection: DatabaseConfig class ThothRestDwhConfig(BaseModel): type: Literal["thoth_rest"] database: DatabaseIdentityConfig endpoint: RestConfig DwhResourceConfig = Annotated[ PostgresDwhConfig | ThothRestDwhConfig, Field(discriminator="type"), ] ``` Translate legacy keys before Pydantic validation and emit one deprecation warning to stderr, never stdout. - [ ] **Step 4: Run all config and workspace tests** Run: `cd harness && .venv/bin/pytest tests/test_config_resources.py tests/test_config_legacy_compat.py tests/test_workspace.py tests/test_vector_dual_key.py -q` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add harness/tht/config.py harness/tht/config_compat.py harness/workspaces/tht.example.yaml harness/tests/test_config_resources.py harness/tests/test_config_legacy_compat.py git commit -m "feat(config): add typed resource schema" ``` ### Task 5: Centralize construction and migrate command call sites **Files:** - Create: `harness/tht/adapters/factory.py` - Modify: `harness/tht/cli/db_cmd.py` - Modify: `harness/tht/cli/schema_cmd.py` - Modify: `harness/tht/cli/sql_cmd.py` - Modify: `harness/tht/cli/lsh_cmd.py` - Modify: `harness/tht/cli/search_cmd.py` - Modify: `harness/tht/cli/vector_cmd.py` - Modify: `harness/tht/cli/memory_cmd.py` - Test: `harness/tests/test_adapter_factory.py` **Interfaces:** - Produces: `build_dwh(cfg: Config) -> DwhAdapter` and `build_vector_store(cfg: Config, *, require_write: bool = False) -> VectorStore`. - Consumes: resource configs from Task 4 and wrappers from Tasks 2-3. Correction: `DwhAdapter.distinct_values(table, column, *, limit)` requires an explicit positive limit, and direct DWH construction injects `cfg.execution.statement_timeout_ms`. Targeted vector writes consume the factory-returned `VectorStore` and pass `VectorWriteRecord` objects to `upsert`. Transitional exception: `build_vector_loader` remains solely for bulk collection sync (`vector init`/rebuild/index flows). It may still construct the legacy table-scoped writer directly until `docs/superpowers/plans/2026-07-11-local-pgvector-profile.md` migrates the local pgvector/vector schema and bulk-sync path. Interactive and targeted writes (`memory save-one` and solved-question indexing) are not covered by this exception and must continue through `build_vector_store(..., require_write=True)` and the public vector port. - [ ] **Step 1: Write exact factory selection and missing-writer tests** ```python def test_factory_selects_http_vector_and_requires_writer(config): assert isinstance(build_vector_store(config), ThothHttpVectorStore) with pytest.raises(ConfigError, match="writer"): build_vector_store(config, require_write=True) ``` - [ ] **Step 2: Verify failure** Run: `cd harness && .venv/bin/pytest tests/test_adapter_factory.py -q` Expected: FAIL because the factory does not exist. - [ ] **Step 3: Implement factory and replace per-command branching** ```python def build_dwh(cfg: Config) -> DwhAdapter: match cfg.dwh.type: case "postgres_direct": return PostgresDwhAdapter(cfg.dwh.connection) case "thoth_rest": return ThothRestDwhAdapter(cfg.dwh.database, cfg.dwh.endpoint) case other: raise ConfigError(f"Adapter DWH non supportato: {other}") ``` Delete transport checks from migrated commands; keep CLI wording and exit codes stable. - [ ] **Step 4: Run focused command suites, full harness suite, and ruff** Run: `cd harness && .venv/bin/pytest tests/test_adapter_factory.py tests/test_schema_introspect_guard.py tests/test_sql_preview_json.py tests/test_search_pack.py tests/integration/test_gate_cli_signatures.py -q` Expected: PASS. Run: `cd harness && .venv/bin/pytest -q` Expected: all non-L2 tests PASS. Run: `cd harness && .venv/bin/ruff check tht tests/test_adapter_factory.py tests/test_dwh_port_contract.py tests/test_dwh_adapters.py tests/test_vector_port_contract.py tests/test_config_resources.py tests/test_config_legacy_compat.py` Expected: no errors. - [ ] **Step 5: Commit** ```bash git add harness/tht harness/tests harness/workspaces/tht.example.yaml git commit -m "refactor(core): route integrations through adapter factory" ```