<?xml version="1.0" encoding="UTF-8"?>
<gsl-prompt id="20250810T024606+0000" type="feat">
<gsl-header>

# CQRS + Strict Typing + SQLite + Clean-on-Top Prompt
</gsl-header>
<gsl-block>

<gsl-purpose>
<gsl-label>

## 1. Purpose
</gsl-label>
<gsl-description>

Define and scaffold a low-risk, incremental path to evolve the OBK CLI into a strictly-typed,
CQRS-friendly architecture with SQLite persistence and a “clean on top, slice under it” structure,
so that the project becomes easier to debug in PyCharm, safer to change, and ready to scale.
</gsl-description>
</gsl-purpose>

<gsl-inputs>
<gsl-label>

## 2. Inputs
</gsl-label>
<gsl-description>

- Current OBK codebase (Typer CLI, DI container, validation & preprocessing utilities).
- Architectural goals: strict typing, CQRS seams (commands/queries), SQLite persistence, clean architecture boundaries.
- Tooling: mypy (strict), ruff, pytest, SQLAlchemy 2.x (or SQLModel), optional Alembic, PyCharm debugger.
- Operational practices: lightweight DI wiring; rotating file logging; reproducible local env via venv.
</gsl-description>
</gsl-inputs>

<gsl-outputs>
<gsl-label>

## 3. Outputs
</gsl-label>
<gsl-description>

#### A. Low-risk implementation plan (ready-to-execute)
1) Strict typing first
   - Add `src/obk/py.typed` (empty file) and include in package.
   - Add `[tool.mypy]` with `strict = true` (and `warn_unreachable`, `warn_unused_ignores`, `disallow_any_generics`, `no_implicit_optional`).
   - Add `[tool.ruff]` and run `ruff + mypy` in CI and pre-commit.
   - Type public functions; prefer `Protocol`, `TypedDict`, `Literal`, `NewType`, `Annotated` where appropriate.

2) Define CQRS seams (ports)
   - `application/ports/` with `Repo` (read/write), `Clock`, optional `Bus`.
   - Domain DTOs as `@dataclass(frozen=True)`; no I/O in domain or application layers.

3) Handlers
   - `application/commands/*` and `application/queries/*` (pure functions taking ports).
   - Unit-test with fakes; no logging or DB usage in handlers.

4) Infrastructure (SQLite now, PG later)
   - `infrastructure/db/` with SQLAlchemy 2.x engine bootstrap:
     - `PRAGMA foreign_keys=ON`, `PRAGMA journal_mode=WAL`, `CREATE TABLE IF NOT EXISTS ...`.
   - `SqlRepo` implements `Repo` using SQLAlchemy Core for writes and ORM (or Core) for reads.
   - Optional Alembic migrations directory.
   - Rotating file handler for logs; initialize only in the executable entrypoint.

5) Interface (clean on top)
   - Typer CLI depends only on application handlers.
   - Move global side effects (excepthook, logging init) into `main()` to avoid import-time side effects.

6) DI wiring
   - `containers.py` provides `config`, `engine`, `repo`, `clock`; CLI resolves dependencies via container.

7) Tests
   - Unit tests for handlers with a `FakeRepo` (in-memory).
   - Integration tests with a temp SQLite file using the real engine bootstrap.
   - (Optional) property-based tests with `hypothesis` for tricky transforms.

8) PyCharm Debugging
   - Add a `__main__`/runner to invoke the Typer app for IDE configs.
   - Provide Run/Debug configs for CLI and pytest; set working dir to repo root and ensure content roots on PYTHONPATH.

#### B. Deliverables
- Updated project layout reflecting `application/`, `domain/`, `infrastructure/`, `interface/` (and optional `slices/<feature>`).
- `pyproject.toml` updated with `mypy`/`ruff` and test extras.
- Minimal `SqlRepo` (SQLite) and smoke tests (`db list`, `db add-*`).
- CI pipeline that fails on type or lint errors and runs unit/integration tests.
</gsl-description>
</gsl-outputs>

<gsl-workflows>
<gsl-label>

## 4. Workflows

</gsl-label>
<gsl-description>

#### A) End-user: Validate & report from GSL (ephemeral DB session)

1. **Start a session**
   `obk db session start --db .obk/session.db --ephemeral`

   * Creates a fresh SQLite database (file-backed for WAL) with `foreign_keys=ON`, `journal_mode=WAL`.
   * Tables are created in **STRICT** mode by default.

2. **Load GSL XML**
   `obk import gsl ./inputs/*.xml --fail-on schema,referential`

   * Parses XML against XSD, loads normalized rows into the ephemeral DB.
   * Enforces FKs and CHECK constraints; rejects invalid rows; emits a validation report.

3. **Run checks & reports**
   `obk report issues --format table`
   `obk report summary --format json`

   * Shows constraint violations, missing references, and cross-file inconsistencies.

4. **End session**
   `obk db session end [--persist]`

   * Drops/cleans the DB by default. Use `--persist` to keep the DB for inspection.

#### B) End-user: Configure integrity rules (no code)

1. **Edit rules** in `obk.rules.yml`: declare required FKs, uniqueness, and allowed enums.
2. **Apply**: `obk db compile-schema --rules obk.rules.yml` (dry-run shows planned DDL).
3. **Run**: `obk db migrate --apply` regenerates STRICT tables with updated constraints.
4. **Validate**: re-run the import + reports; diffs are shown between rule sets.

#### C) Developer: Add a **Command** (CQRS write)

Goal: expose a state-changing operation to users (e.g., “accept a prompt”).

1. **Scaffold**
   `obk dev new-command AcceptPrompt`
   Creates:

   * `application/commands/accept_prompt.py` (handler skeleton)
   * `tests/unit/commands/test_accept_prompt.py` (with FakeRepo)
   * CLI wire stub under `interface/cli/`

2. **Define types**

   * Add a `@dataclass(frozen=True)` DTO in `application/ports/dto.py`.
   * Mypy enforces correct usage across handler and CLI glue.

3. **Implement & test**

   * Implement handler logic (pure).
   * Run `pytest -q && mypy`.

4. **Expose to users**

   * Typer verb added (e.g., `obk prompt accept --id ...`).

#### D) Developer: Add a **Query** (CQRS read)

Goal: return projections without mutating state.

1. **Scaffold**
   `obk dev new-query ListPrompts` → creates handler + unit test + CLI stub.

2. **Shape the result**

   * Define a typed return (e.g., `Iterable[Prompt]`) in ports.
   * Optional: add a view model/dataclass for projection results.

3. **Implement & test**

   * Use Repo read methods; no DB code in handler.
   * `pytest -q && mypy`.

4. **Expose to users**

   * Typer verb (e.g., `obk prompt list --since 2025-01-01`).

#### E) Developer: Use **strict typing** effectively

1. **Create/extend DTOs** with `@dataclass(frozen=True)` and precise types (`Literal`, `Annotated`).
2. **Protocols for ports** ensure handlers depend on interfaces, not implementations.
3. **Run gates**: `mypy --strict` must pass before merging; PyCharm shows in-editor type errors.
4. **Refactor safety**: changing a DTO/port immediately highlights all call sites.



#### F) Developer: SQLite schema & constraints (runtime, not static)

1. **Enable STRICT tables** (recommended)

   * Default table DDL uses `CREATE TABLE ... STRICT;`
   * Keep `PRAGMA foreign_keys=ON` for all connections.

2. **Create/modify tables**

   * Declarative file first: edit `db.schema.yml` (columns, PKs, FKs, CHECKs, indexes).
   * Compile to DDL: `obk db compile-schema --rules db.schema.yml --preview`.
   * Apply: `obk db migrate --apply` (on ephemeral session DB).

3. **Validate referential integrity** during import

   * On `obk import gsl`, rows that violate FKs/CHECKs are rejected with a line-level report.
   * Use `--defer` to allow staged loads with `DEFERRABLE INITIALLY DEFERRED` constraints.

4. **Ephemeral vs persisted**

   * Default is ephemeral (clean at `session end`).
   * Use `--persist` to keep state for multi-step analysis or debugging.

#### G) Developer: PyCharm debug workflow (language-agnostic)

1. **Run config**: script `python -m obk.interface.cli` with args (e.g., `import gsl file.xml`).
2. **Breakpoints** in handlers (`application/commands/*`, `application/queries/*`).
3. **Step-through** stays in the application layer unless drilling into infrastructure.
4. **Inspect DB**: open `.obk/session.db` with the IDE database tools between steps.

#### H) Developer: Vertical Slice workflow (inside Clean layers)

1. **Scaffold a slice**
   `obk dev new-slice prompts`
   Creates folders:

   * `domain/prompts/` (entities/value objects)
   * `application/prompts/` (commands/queries handlers)
   * `infrastructure/prompts/` (repo adapters, DDL fragments)
   * `interface/cli/prompts.py` (Typer verbs)

2. **Add a command or query to the slice**

   * `obk dev new-command prompts AcceptPrompt`
   * `obk dev new-query prompts ListPrompts`
     Handlers are wired to ports; CLI imports only handlers.

3. **Slice-local schema**

   * Edit `slices/prompts/db.schema.yml` and recompile:
     `obk db compile-schema --slice prompts --preview`
   * Apply to the current session DB:
     `obk db migrate --slice prompts --apply`

4. **Test the slice in isolation**

   * Unit: `pytest tests/slices/prompts/unit -q`
   * Integration (ephemeral SQLite): `pytest tests/slices/prompts/integration -q`

5. **Enforce boundaries**

   * Optional: `import-linter` contracts ensure `interface → application → domain`, and infra depends inward only.

</gsl-description>
</gsl-workflows>

<gsl-acceptance-tests>
<gsl-label>

## 5. Acceptance Tests
</gsl-label>

<gsl-acceptance-test id="1">
<gsl-performed-action>

1. When I run `mypy` and `ruff` in CI on a fresh clone.
</gsl-performed-action>
<gsl-expected-result>

    Then the pipeline fails on any type or lint error, blocking release.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="2">
<gsl-performed-action>

2. When I call a Typer command that triggers an application handler.
</gsl-performed-action>
<gsl-expected-result>

    Then the handler executes without importing infrastructure directly, resolving dependencies via the container only.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="3">
<gsl-performed-action>

3. When I run unit tests for commands/queries with a FakeRepo.
</gsl-performed-action>
<gsl-expected-result>

    Then tests run without touching the filesystem or opening a DB connection.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="4">
<gsl-performed-action>

4. When I run integration tests with SQLite.
</gsl-performed-action>
<gsl-expected-result>

    Then the engine applies PRAGMAs and creates tables; CRUD paths pass; logs rotate on rollover without import-time side effects.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="5">
<gsl-performed-action>

5. When I launch the CLI via a PyCharm Run/Debug configuration and hit a breakpoint in a handler.
</gsl-performed-action>
<gsl-expected-result>

    Then execution stops in the handler (not CLI glue), variables are inspectable, and stepping does not cross into infrastructure unless invoked.
</gsl-expected-result>
</gsl-acceptance-test>
</gsl-acceptance-tests>
</gsl-block>
</gsl-prompt>
