# System Instructions

This prompt is divided into three sections:

1. **System Instructions** (this section) — structural orientation only. Do not treat this
   section as task input.

2. **Input Context** — begins with the heading `# Input Context`. All blocks are wrapped in
   `<pblock>` tags. Two block types:
   - File blocks: `<pblock filename="<name>" role="<role>" guidance="...">` — optional
     `guidance` attribute carries context-specific instructions; content is in a fenced block.
   - Metadata/section blocks: `<pblock label="<label>" kind="<kind>">` — job parameters,
     rules, instructions, or group headers.

3. **Agent Task** — begins with the heading `# Agent Task`. Defines your persona, constraints,
   and required outputs. Read all input context before acting on this section.


# Input Context

<pblock label="Build block job" kind="job">
## Build block job
- TARGET: ReadingList
- BUILD_DIRECTORY: /mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList
- WORKING_DIRECTORY: /mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList
- BUILD_BLOCK: Block 2 · Foundational (block-2)
- STORIES: Establish SQLite persistence for ordered books. (database)
- DATE: 2026-08-15
- BUILD_SCOPE: exactly one MANIFEST.md build block

</pblock>

<pblock label="Stories in this block" kind="section">
## Stories in this block
- Establish SQLite persistence for ordered books. (database) [story]

</pblock>


## COMPASS - Target Orientation

<pblock filename="COMPASS.md" role="compass" path="/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/COMPASS.md" guidance="Important: This is the core project intent, constraints, and guardrails. It should have presecedence in conflicts.">
```markdown
# COMPASS: ReadingList

## Compass
ReadingList is a small web application for readers who want to maintain a personal, ordered list of books to read. A reader can add a book with its title and author, review books in insertion order, and remove books when they are no longer needed.

## Constraints
- The application must run as a web application.
- Book submissions require both a title and an author.
- Books must be shown in the order they were added.
- `bin/test.sh` must be POSIX-compatible and run from the application root.
- The complete automated test suite must pass for `bin/test.sh` to exit zero.

## Guardrails
- Never accept or store a submission with an empty title or author.
- Never reorder books relative to their addition order.
- Never report the test command as successful when any automated test fails.
- Always provide a clear reason when rejecting incomplete input.

<!-- drydock:build-write-guardrail:start -->
## Build Write Guardrail

- Authorized build directory: `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList`
- Authorized Target directory: `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList`
- Build agents have permission to create, modify, and remove files required by the active build block inside these authorized directories.
- No path outside these authorized directories may be modified.
- Protected Drydock artifacts:
  - `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/blueprint/`
  - `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/MANIFEST.md`
  - `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/COMPASS.md`
  - `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/QuarterDeck/`
  - `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/evidence/`
<!-- drydock:build-write-guardrail:end -->

<!-- Drydock author intent sha256=c86a1a56f7d25ca5548192dbe12c57989f5b1ddd34835753e64e7deb83845e3a source=reading-list.md -->

# Reading List

Build a web application that keeps a list of books to read.

The reader can add a book with a title and author, view the books in the order added,
and remove a book. An empty title or author is rejected with a clear error message.

The application includes automated tests for each behavior.

The completed application provides a POSIX-compatible `bin/test.sh` that runs the complete
automated test suite from the application root. `sh bin/test.sh` exits zero only when every test
passes. The final build story runs this command after every implementation story and preserves its
command, exit code, standard output, and standard error as evidence.
```
</pblock>


## STACK - Technology HOW

<pblock filename="persistence.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/persistence.md">
````
# Persistence & Service Encapsulation

**Version:** 20260603 V1
**Category:** Persistence
**Description:** The single boundary rule for all persistent stores and external services — typed access classes, no raw access in application code

Technology reference. Framework-agnostic. This file does not change between projects.

Prerequisite: `stack/common.md`

---

## The Invariant

**Rule**: All persistence and external-service access goes through a typed encapsulation
class. Route, feature, and business-logic code never executes raw SQL, reads `os.environ`,
opens application files directly, or imports a cloud SDK (`boto3`). The typed class interface
— not the underlying schema or service — is the dependency boundary.

**Why**: A storage or service change is absorbed at the class. Downstream code depends on the
interface, so a schema change that does not change the interface changes nothing else. This is
the same swap-layer discipline a cloud client library enforces for AWS (`import boto3`
outside the wrapper fails review), generalized to every store.

A code review that finds raw SQL, `os.environ`, `open()` on application data, or a cloud SDK
import outside its encapsulation class fails.

**ORM exception**: where an ORM is used (Django, SQLAlchemy), the ORM model class *is* the
table encapsulation and satisfies §1 — do not add a second hand-written layer.

---

## 1. Relational tables → typed Database class

Each table gets a row dataclass and a CRUD class. One `Database` class composes them and owns
the connections. Application code calls `db.<table>.method()` — never raw SQL.

**Caller contract**: `Database` presents single-threaded semantics. Callers construct one
instance, share it freely, call methods synchronously, and never reason about threads,
connections, or locking. Thread handling is internal to the class and may change without
affecting any caller. Instance methods are safe to call concurrently; returned rows are plain
dataclasses and carry no connection state. Everything below is how the class keeps that promise —
a different implementation is acceptable only if it keeps it.

**Connection lifetime rule**: `Database` owns **one connection per workflow**, opened on first use
in that workflow. A connection is never stored on the instance and never handed to a table class;
table classes receive a *connect provider* and call it on every operation.

This is not concurrency. Queries stay sequential within a workflow — the rule only stops one
connection from being shared across unrelated workflows. The mechanism is `threading.local()`
because a web server is what defines a workflow: the application is constructed on the main
thread and each request is dispatched on a worker thread, whether or not the application code
ever uses threads itself. A `sqlite3.Connection` belongs to the thread that created it and raises
`ProgrammingError` when used from another, so a single connection opened during construction is
valid in no request at all.

```python
# db.py
import sqlite3, threading
from dataclasses import dataclass
from pathlib import Path

@dataclass
class Item:
    id: int | None
    name: str
    status: str = "active"
    created_at: str | None = None

class ItemTable:
    def __init__(self, connect): self._connect = connect   # provider, not a connection
    def create(self, name: str, status: str = "active") -> int:
        c = self._connect()
        cur = c.execute(
            "INSERT INTO items (name, status) VALUES (?, ?)", (name, status))
        c.commit(); return cur.lastrowid
    def get(self, item_id: int) -> Item | None:
        r = self._connect().execute(
            "SELECT * FROM items WHERE id = ?", (item_id,)).fetchone()
        return Item(**r) if r else None
    def list(self) -> list[Item]:
        return [Item(**r) for r in
                self._connect().execute("SELECT * FROM items").fetchall()]
    def update(self, item_id: int, **fields) -> None: ...
    def delete(self, item_id: int) -> None: ...

class Database:
    def __init__(self, path: str):
        self._path = path
        self._local = threading.local()
        self.items = ItemTable(self.connect)   # one attribute per table

    def connect(self) -> sqlite3.Connection:
        """The calling thread's connection, opened and configured on first use."""
        conn = getattr(self._local, "conn", None)
        if conn is None:
            if self._path != ":memory:":
                Path(self._path).parent.mkdir(parents=True, exist_ok=True)
            conn = sqlite3.connect(self._path)
            conn.row_factory = sqlite3.Row
            conn.execute("PRAGMA journal_mode=WAL")
            conn.execute("PRAGMA foreign_keys=ON")
            self._local.conn = conn
        return conn
```

Schema creation and migrations run through `self.connect()` like every other operation, on
whichever thread starts the application. That startup connection is not the one request handlers
use, and nothing needs to share it.

PRAGMAs, JSON-column handling, and migrations (see `stack/sqlite.md`) live inside
`Database`/the table classes, never in callers.

---

## 2. Config / `.env` → one typed Config class

One typed `Config` reads the environment once and validates at startup. Nothing else reads
`os.environ`. There are no `Dev`/`Prod`/`Test` subclasses — every field is inherited from the
environment, so the environment (`.env`) selects the configuration, not a Python subclass.

```python
# config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()

@dataclass(frozen=True)
class Config:
    secret_key: str
    database_path: str
    port: int
    debug: bool = False

    @classmethod
    def load(cls) -> "Config":
        try:
            return cls(
                secret_key=os.environ["SECRET_KEY"],
                database_path=os.environ.get("DATABASE_PATH", "data/app.db"),
                port=int(os.environ.get("APP_PORT", "5001")),
                debug=os.environ.get("APP_DEBUG") == "1",
            )
        except KeyError as e:
            raise RuntimeError(f"Missing required env var: {e}") from e
```

Typed fields mean a missing or malformed variable crashes at startup, not at first use.

---

## 3. Files → typed FileStore class

A `FileStore` owns a root directory and is the only code that opens application files. Callers
never build paths or call `open()`/`shutil` directly.

```python
# filestore.py
from pathlib import Path

class FileStore:
    def __init__(self, root: str): self._root = Path(root)
    def read(self, name: str) -> str:
        return (self._root / name).read_text(encoding="utf-8")
    def write(self, name: str, data: str) -> None:
        p = self._root / name; p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(data, encoding="utf-8")
    def list(self, pattern: str = "*") -> list[str]:
        return [p.name for p in self._root.glob(pattern)]
```

Path-traversal validation and atomic writes live here, once.

---

## 4. External services → wrapper modules over a shared base class

Every external-service wrapper extends one `ServiceClient` base class. The base owns the
backend handle, a named logger, and a uniform call guard; subclasses expose typed methods and
never leak the SDK client.

```python
# service.py — base class for all external-service wrappers
import logging

class ServiceClient:
    """Base for service wrappers. Owns the backend handle, logger, and a uniform
    call guard. Subclasses expose typed methods; they never expose the SDK client."""
    def __init__(self, backend, name: str | None = None):
        self._backend = backend
        self.log = logging.getLogger(name or type(self).__name__)

    def _guard(self, op, *args, **kwargs):
        try:
            return op(*args, **kwargs)
        except Exception:
            self.log.exception("%s call failed", type(self).__name__)
            raise
```

Wrappers subclass it. AWS commonly uses a project-owned cloud client library
(`client.queue`, `client.share`, `client.catalog`); any new service follows the same shape — e.g.
a `MessageBus` over SQS, even when it maps 1:1:

```python
# messagebus.py — the wrapper hides the transport
class MessageBus(ServiceClient):
    def publish(self, topic: str, payload: dict) -> str:
        return self._guard(self._backend.send, topic, payload)
    def subscribe(self, topic: str, handler) -> None: ...
```

**Why**: the base class makes logging and error handling uniform across services; the wrapper
is the seam. Swapping the transport, or stubbing it in tests, changes one class. See
`stack/cloud-client-library.md`, `stack/aws-sqs.md`, `stack/aws-s3.md`, `stack/aws-dynamodb.md`.

---

## Summary Checklist

- [ ] No raw SQL outside table/Database classes
- [ ] No `os.environ` reads outside the Config class
- [ ] No `open()`/`shutil` on application data outside FileStore
- [ ] No cloud SDK import outside its service wrapper
- [ ] Each table: row dataclass + CRUD class, composed in one Database class
- [ ] No `sqlite3.Connection` stored on an instance or passed to a table class — one connection
      per thread, table classes take a connect provider
- [ ] One typed `Config`, ENV-driven, no Dev/Prod/Test subclasses
- [ ] Service wrappers extend the `ServiceClient` base class
- [ ] Downstream specs depend on the class interface, not the schema
````
</pblock>

<pblock filename="sqlite_compact.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/sqlite_compact.md">
```
<!-- Compacted from Rigging/stack/sqlite.md sha256=cadcfa52c5fa9cbf8a0a92b6a8d230d4888e584b888aaf933c24579f32f651ce on 2026-08-06 by drydock rigging compact — regenerate with: drydock rigging compact --include-file {rel_source} -->

# SQLite Best Practices — Contract Surface

## Connection

### get_db
Opens and configures a SQLite connection for the supplied database path.

| Input | Type | Required |
|---|---|---|
| `db_path` | path | Yes |

Returns a configured `sqlite3.Connection`.

Constraints: Enable WAL mode, foreign-key enforcement, and `sqlite3.Row` row factory. Use one connection per thread; do not share connections across threads or use `check_same_thread=False` as a substitute.

## Schema

### Rule: schema-types
Defines dates as ISO 8601 `TEXT`, booleans as `INTEGER`, and extensible fields as JSON stored in `TEXT` columns.

### items
The `items` table contains the following fields:

| Field | Type | Constraints / Default |
|---|---|---|
| `id` | `INTEGER` | Primary key |
| `name` | `TEXT` | Unique, not null |
| `status` | `TEXT` | Default `'active'` |
| `extra` | `TEXT` | JSON, default `'{}'` |
| `created_at` | `TEXT` | Default current datetime |
| `updated_at` | `TEXT` | Default current datetime |

### get_extra
Parses an item's `extra` field as JSON.

| Input | Type | Required |
|---|---|---|
| `row` | SQLite row containing `extra` | Yes |

Returns a JSON object, defaulting to `{}` when the field is empty.

### set_extra
Updates one key in an item's JSON `extra` field and commits the change.

| Input | Type | Required |
|---|---|---|
| `db` | database connection | Yes |
| `item_id` | item identifier | Yes |
| `key` | JSON object key | Yes |
| `value` | JSON-compatible value | Yes |

## Queries

### row_to_dict
Converts a SQLite row to a dictionary and parses the `extra` JSON field when present.

| Input | Type | Required |
|---|---|---|
| `row` | SQLite row or `None` | Yes |

Returns a dictionary or `None`.

### query
Executes a parameterized SQL query and returns converted rows.

| Input | Type | Default |
|---|---|---|
| `db` | database connection | Required |
| `sql` | SQL string | Required |
| `params` | query parameters | `()` |
| `one` | boolean | `False` |

Returns one dictionary or `None` when `one=True`; otherwise returns a list of dictionaries.

### execute
Executes a parameterized SQL statement and commits it.

| Input | Type | Default |
|---|---|---|
| `db` | database connection | Required |
| `sql` | SQL string | Required |
| `params` | query parameters | `()` |

Returns the statement's `lastrowid`.

### Rule: parameterized-queries
All SQL queries must use parameters; user input must never be interpolated into SQL.

## Migrations

### _run_migrations
Adds missing columns to the `items` table and commits the migration transaction.

Constraints: Run at startup and detect existing columns with `PRAGMA table_info()`; migrations must be idempotent.

### _table_exists
Checks whether a named SQLite table exists.

| Input | Type | Required |
|---|---|---|
| `db` | database connection | Yes |
| `table_name` | table name | Yes |

Returns a boolean.

## Backup

### backup_database
Checkpoints the database WAL and copies the database file into a timestamped backup.

| Input | Type | Default |
|---|---|---|
| `db_path` | database path | Required |
| `backup_dir` | backup directory | `'data/backups'` |

Returns the backup file path.

Constraints: Checkpoint the WAL before copying; create the backup directory when absent.
```
</pblock>

<pblock filename="python_compact.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/python_compact.md">
````
<!-- Compacted from rigging/stack/python.md on 2026-07-16 (manual update to match python.md V3) -->

# Python — Compact

## Configuration Management

One typed frozen-dataclass `Config` is the only env reader — never read `os.environ` elsewhere. No `Dev`/`Prod`/`Test` subclasses; the environment (`.env`) selects configuration. Never hardcode secrets, ports, or paths. Secret hygiene and `.env.example`: see `stack/env_variables_and_secrets.md`.

```python
# config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()

@dataclass(frozen=True)
class Config:
    secret_key: str
    database_path: str
    port: int
    debug: bool = False

    @classmethod
    def load(cls) -> "Config":
        try:
            return cls(
                secret_key=os.environ["SECRET_KEY"],
                database_path=os.environ.get("DATABASE_PATH", "data/app.db"),
                port=int(os.environ.get("APP_PORT", "5001")),
                debug=os.environ.get("APP_DEBUG") == "1",
            )
        except KeyError as e:
            raise RuntimeError(f"Missing required env var: {e}") from e
```

## Code Style and Understandability

Code must be understandable through naming, structure, small focused units, explicit types, clear interfaces, appropriate abstractions, and tests. Names state intent; one responsibility per module; functions do one thing at one level of abstraction; no speculative abstraction layers. Comments state constraints the code cannot express — never restate mechanics.

## Type Hints and Static Typing

Modern hints on all public interfaces; typed structures across boundaries; run a type checker when practical.

- Built-in generics (`list[str]`, `dict[str, int]`) and `X | None` — never `typing.List`, `Optional`, `Union`
- Type every public function, method, and class attribute
- Schemas/serializers/services/data structures are typed classes: frozen dataclasses internally, Pydantic/`TypedDict` at serialization boundaries
- No bare `dict`, positional tuples, or `Any` crossing a module boundary
- `uv add --dev mypy` then `uv run mypy .` (or pyright) alongside ruff and pytest in CI

## Logging

Use `logging` with named loggers, never `print()`. Configure formatter + console and file handlers at startup (`data/logs/app.log`); level from `APP_DEBUG`.

```python
import logging
logger = logging.getLogger(__name__)
logger.info('Server starting on port %s', port)
```

## Environment Separation

Distinct `.env` per environment; same typed `Config` reads whichever is present. Never run debug in production.

| Setting | Dev | Test | Prod |
|---------|-----|------|------|
| APP_DEBUG | 1 | 0 | 0 |
| DATABASE_PATH | data/app.db | :memory: | data/app.db |
| SECRET_KEY | .env value | .env value | .env value (required) |
| LOGGING | DEBUG | WARNING | INFO |

## Testing

`pytest` with fixtures; fresh in-memory DB per test; test at the boundary. Every Python build must include a complete pytest suite regardless of the specification — no tests, no ACTIVE conformity.

```python
# tests/conftest.py
import pytest

@pytest.fixture
def app(monkeypatch):
    monkeypatch.setenv("SECRET_KEY", "test")
    monkeypatch.setenv("DATABASE_PATH", ":memory:")
    from app import create_app
    from config import Config
    yield create_app(Config.load())

@pytest.fixture
def client(app):
    return app.test_client()
```

**test_smoke.py** — app factory works; `GET /health` returns 200 `{"status": "ok"}`; `GET /` returns 200.

**test_routes.py** — one test per route: `GET` pages assert 200; `POST` APIs assert status in `{200, 201, 204}`; HTMX routes send `HX-Request: true`; `{id}` routes use fixture-created records.

**test_db.py** (only if DATABASE.md exists) — expected tables exist; round-trip per major table; invalid FK raises `IntegrityError` (`PRAGMA foreign_keys=ON`).

Do not test third-party internals, config loading, or private helpers.

```ini
# pytest.ini
[pytest]
testpaths = tests
addopts = -v
```

## Security

Validate all user input; parameterized queries exclusively; never trust client data.

- `?` placeholders, never f-strings, for all DB operations
- `secure_filename()` for user-supplied paths
- Length and type validation on inputs
- Secret key from environment, never hardcoded
- Never expose stack traces to end users

## Dependency Management (uv)

`uv` only; `pyproject.toml` is the manifest; `uv.lock` committed; `.venv/` gitignored. Full toolchain conventions: `stack/uv_ruff.md`.

```bash
uv venv                          # creates .venv/
uv add flask python-dotenv       # runtime deps → pyproject.toml + uv.lock
uv add --dev pytest ruff mypy    # dev deps
uv sync --frozen                 # CI — fail if lock is stale
```

- Never bare `pip install` or `python -m venv`
- Dev deps in `[project.optional-dependencies].dev`; runtime deps minimal

## Startup Validation

`Config.load()` already validates required env vars; startup validation confirms DB connectivity. Crash early on misconfiguration.

```python
def validate_startup(config: Config, db: Database):
    try:
        db.healthcheck()          # SELECT 1 inside the Database class
    except Exception as e:
        raise RuntimeError(f'Database not accessible: {e}')
    logger.info('Startup validation passed')
```

## Directory Layout

```
project-name/
├── app.py              # Entry point / app factory
├── routes.py           # Route handlers
├── models.py           # Data models and type registries
├── db.py               # Database class: typed tables, connection, schema, migrations
├── ops.py              # Business logic and operations
├── config.py           # typed Config class — the only env reader
├── templates/          # base.html + types/ partials
├── static/             # css/, js/
├── tests/              # conftest.py, test_*.py
├── bin/                # (from common.md)
├── data/               # (from common.md)
├── pyproject.toml
├── uv.lock
├── .env                # gitignored; .env.example committed
└── .gitignore
```
````
</pblock>


## IMPLEMENTS - Authoritative Step Specifications

<pblock label="Implementation recency anchor" kind="section">
The files in this section are the load-bearing specifications for this build block.
Build these files exactly. Treat earlier sections as constraints and context.

</pblock>

<pblock filename="DATABASE.md" role="implements" path="/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/blueprint/DATABASE.md" guidance="Database and PersistenceSpecification">
```markdown
# DATABASE: Book Persistence

| Field       | Value |
|-------------|-------|
| Version     | 20260815 V1 |
| Description | Defines the SQLite persistence contract for ordered reading-list books. |
| Depends On  | ARCHITECTURE.md |
| Provides    | book_store.add, book_store.list_ordered, book_store.remove, books_table |
| Consumes    | application_factory |

## Access Patterns

| Caller | Operation | Store | Interface |
|---|---|---|---|
| Book creation workflow | Add a submitted title and author | `books` | `BookStore.add(title, author)` |
| Ordered-list workflow | Read all books | `books` | `BookStore.list_ordered()` |
| Book-removal workflow | Delete a selected book | `books` | `BookStore.remove(book_id)` |

## Persistence Interfaces

| Store | Public interface | Module | Allowed callers | Notes |
|---|---|---|---|---|
| SQLite `books` table | `BookStore.add(title, author) -> Book` | `app.persistence` | Application workflows | Persists one book. |
| SQLite `books` table | `BookStore.list_ordered() -> list[Book]` | `app.persistence` | Application workflows | Returns rows in insertion order. |
| SQLite `books` table | `BookStore.remove(book_id) -> bool` | `app.persistence` | Application workflows | Removes the selected row and reports whether it existed. |

## Schema

The `books` table contains:

- `id`: integer primary key.
- `title`: required text.
- `author`: required text.
- `created_at`: persisted timestamp or equivalent insertion marker.

The primary key is monotonically assigned by SQLite and is used to preserve insertion order. The database must reject null title or author values. Application validation additionally rejects empty submitted values.

## Configuration

The application factory supplies the database location through Flask configuration. Tests may provide an isolated temporary path or an in-memory database. Connections are scoped to the application context and closed after use.

## Migrations and Initialization

Application startup creates the `books` table when it does not exist. Initialization is idempotent and must not delete existing rows.

## Programmatic Acceptance

=== AC database-add-readback ===
Intent: A book added through the persistence interface can be read back with the submitted fields.

from app import create_app
from app.persistence import get_book_store

title = "The Dispossessed"
author = "Ursula K. Le Guin"
application = create_app({"TESTING": True, "DATABASE": ":memory:"})

with application.app_context():
    store = get_book_store()
    created = store.add(title, author)
    books = store.list_ordered()

assert len(books) == 1
assert books[0].id == created.id
assert books[0].title == title
assert books[0].author == author
=== END AC database-add-readback ===

=== AC database-order ===
Intent: Ordered reads preserve the order in which books were added.

from app import create_app
from app.persistence import get_book_store

first_title = "A"
first_author = "Author A"
second_title = "B"
second_author = "Author B"
application = create_app({"TESTING": True, "DATABASE": ":memory:"})

with application.app_context():
    store = get_book_store()
    store.add(first_title, first_author)
    store.add(second_title, second_author)
    books = store.list_ordered()

assert [book.title for book in books] == [first_title, second_title]
assert [book.author for book in books] == [first_author, second_author]
=== END AC database-order ===

=== AC database-remove ===
Intent: Removing an existing book makes it absent from a subsequent persistence read.

from app import create_app
from app.persistence import get_book_store

title = "To Remove"
author = "Author"
application = create_app({"TESTING": True, "DATABASE": ":memory:"})

with application.app_context():
    store = get_book_store()
    created = store.add(title, author)
    removed = store.remove(created.id)
    books = store.list_ordered()

assert removed is True
assert books == []
=== END AC database-remove ===

=== AC database-empty ===
Intent: A new database returns an empty ordered collection.

from app import create_app
from app.persistence import get_book_store

application = create_app({"TESTING": True, "DATABASE": ":memory:"})

with application.app_context():
    books = get_book_store().list_ordered()

assert books == []
=== END AC database-empty ===

## User Acceptance

- None.

## Guardrails

- All SQLite access is encapsulated by `app.persistence`.
- Empty titles and authors must never be stored.
- Ordered reads must preserve addition order.
```
</pblock>

<pblock label="Build instructions" kind="instructions">
### Build instructions for this block

#### Establish SQLite persistence for ordered books. (database)
Implement the SQLite persistence boundary for books. Define the books table and the typed store
interface for adding, ordered listing, and removal. Ensure application code reaches SQLite only
through the persistence interface and that insertion order is preserved.

</pblock>

<pblock label="Reusable compact request" kind="section">
## Reusable compacts

The Blueprint sources below are consumed as context by later Manifest blocks.
In this same response, extract their consumer-facing contract surface. Preserve
interfaces, schemas, constraints, configuration, and cross-file obligations; drop
implementation narrative and repetition. Do not write these files yourself.

Before the required RESULT block, emit one optional payload per source exactly as:
<reusable-compact filename="SOURCE.md">
compact content
</reusable-compact>

Emit no payload when a source has no useful technical surface. These payloads are
advisory and do not change the required build result or file-change report.

Sources eligible for reusable compaction:
- DATABASE.md

</pblock>


# Agent Task

You are a Drydock build agent implementing exactly one build step of a larger plan.
The build job block below names the target, the build working directory, and the
step. Everything you need is stacked into this prompt under role headings:

- `compass` — the Target's COMPASS.md orientation.
- `implements` — the Typed Specification files this step builds. These are
  authoritative; implement them exactly.
- `context` — read-only support specifications. Do not reimplement them.
- `stack` — enterprise stack and technology rules. Honor them.
- `rules` — governance and branding rules. Honor them.

Operating contract:

1. Follow the write authorization and protected paths in the stacked `COMPASS.md` exactly.
   That persisted guardrail is the sole authority for paths this build may modify.
2. Start by inspecting the build working directory. Preserve existing application
   files unless this step's specifications require a change.
   Its `sources/` subdirectory holds staged build assets — imported test corpora,
   conformance harnesses, and fixtures — placed there for you. They are read-only
   inputs: run them, import them, and write code against them, but never create,
   rewrite, trim, regenerate, or substitute one, even to make a check pass. A step
   that modifies a staged asset fails and the asset is restored. If an asset you
   expect is absent, report that; do not author a replacement.
3. Implement only this step. Use `context`, `stack`, and `rules` as constraints,
   not as additional work to perform.
4. Follow the stack and rules for languages, structure, naming, and branding.
5. The programmatic acceptance assertions in the `implements` specifications are
   this step's **Definition of Done** — human-owned, declared before the build,
   and fixed. Build the story and, in this same step, write the deterministic
   tests that prove each declared assertion, as a TDD master would; add finer
   tests for coverage. Every test you write follows the same rule the acceptance
   assertions do: **act on the system, read the state back, compare to expected.**
   The oracle is a return value, parsed JSON, a status code, a stored row, file
   contents read back, or an exit status — never a substring of captured stdout or
   stderr, a test-runner tally, or a log line. Write tests in the project's own
   language using that language's libraries; an in-language HTTP client yields a
   status code and a parsed body, where `curl` yields text to scrape. Round-trip
   anything that stores state: act, then read back through the public interface.
   Assert declared failure signals on negative paths, never message wording. You may add tests but must never remove, soften, or weaken
   a declared acceptance assertion. A `Suite: full` conformance check gates on the
   entire imported test suite: the step is done only when it passes in full, never on a
   representative subset — reproduce the standard exactly rather than wrapping a
   third-party library that approximates it. For a suite, the runner's exit status is the
   verdict and the whole verdict: print its captured output for diagnosis, never assert on the
   text of its summary. When an assertion is a static or filesystem
   scan (import boundary, "X never appears outside Y," grep/AST gate), honor the
   scope the specification states and never widen it: scan production source only,
   exclude `.venv/`, `site-packages`, and vendored or generated code, and do not
   flag test doubles or fixtures that use the guarded dependency.
   Run every declared acceptance assertion before returning. For a conformance suite,
   use its section or example filters to diagnose coherent root-cause clusters, but
   rerun the full declared scope before reporting the result. Treat failing examples as
   a work queue for fixing general behavior; never add example-specific exceptions.
6. Grow the project's own test suite as you write the code, and treat it as the project's
   real coverage. The acceptance assertions in `implements` are gates: few, fixed, and
   written before any code existed, so every expectation in them is a prediction. The tests
   you write are written *beside* the finished code, so their expected values are observed
   rather than predicted — which is why exhaustive coverage belongs here and not there.
   Extend the suite in the project's established location and runner, keep it runnable by the
   project's declared test command, and leave it green when you return.
   Cover, at minimum: every public entry point and every verb it declares, including declared
   error paths; the boundaries — empty, exactly one, many, absent optional fields, declared
   maxima; declared idempotence, applied twice; one behavior per test, named for the behavior;
   and isolation — each test arranges its own data, with a fresh store or explicit teardown, so
   a run leaves no residue behind in the build directory.
   Where a staged authoritative suite already covers a surface, that suite is the coverage:
   run it, and do not restate its cases. Report the suite's pass/fail counts in your `SUMMARY`
   so a reader can see coverage moving across steps.
7. Treat `User Acceptance` entries as review evidence requirements. Implement
   the supporting behavior, but do not claim to have performed human judgment.
8. The `implements` section is authoritative and intentionally stacked late in
   the prompt as the recency anchor. Build that WHAT exactly; do not substitute
   generic framework defaults.
9. Before adding or installing Python dependencies, verify each package name
   against the declared registry. Do not invent package names. If a needed
   package cannot be verified or appears newly published, fail explicitly
   instead of installing it.
10. Use the stack's required package manager workflow for dependency changes.
   When the stack requires `uv`, update manifests through `uv` conventions
   rather than bare `pip install`.
11. Do not claim success unless you actually created or modified project files in
   the build working directory. If you cannot write files or cannot complete the
   step, report failure explicitly.
12. Do not run `git add`, `git commit`, create branches, create tags, rewrite
   history, or otherwise mutate Git history. Drydock owns the final build
   directory commit after you return.
13. End your response with this exact closing structure:

```text
RESULT: SUCCESS | FAILED

FILES CHANGED:
- relative/path

SUMMARY:
<brief reviewable summary>

BLOCKERS:
- <only if any>
```
   Before `RESULT`, you may emit one optional JSON payload when implementation required a bounded
   choice not already settled by the owning specification. This records what you did; it does not
   ask permission, create a questionnaire, or excuse incomplete work:

```text
<blueprint-decisions>
[{"spec":"FEATURE-Example.md","severity":"Material","subject":"Chosen behavior","decision":"Options A and B were available. I implemented B because ... Is that acceptable, or should this change on replan?"}]
</blueprint-decisions>
```

   Name only a specification implemented by this build block. Use `Low` or `Material`; Build never
   emits a Blocking decision. Omit the payload when no implementation decision was necessary.

14. `FILES CHANGED` must list only files actually written in the build working
   directory. If no files were written, use `RESULT: FAILED`.
15. On `RESULT: FAILED`, append two additional lines so the failure is actionable
   without opening logs. `FAILURE_SUMMARY` is one line naming the cause;
   `FAILURE_DETAIL` states what happened, why, and what to change before a rerun.
   Name concrete conditions when they apply: token or context limit exceeded,
   could not execute commands in this environment, a required input was missing,
   or a specific tool or command failed.

```text
FAILURE_SUMMARY: <one line naming the cause>
FAILURE_DETAIL: <what happened, why, and what to change before rerunning>
```

16. When a declared acceptance criterion cannot pass no matter how the code is written,
   say so with this exact token. You may not edit the criterion — it is staged and
   restored before grading:

```text
AC_BROKEN: <check-id>[, <check-id>]
```

   This is a report, not a verdict, and it stops nothing. A criterion reaches you only
   when its expected value is one its author could not have invented — a status code, a
   staged suite's exit status, a value the criterion itself supplied as input — so your
   claim that the criterion rather than the code is at fault is the less likely
   explanation, and the budget is spent as it would be for any other failure. A
   criterion whose expectation *was* hand-typed already settles `DISPUTED` on its own,
   without you naming it. Emit the token only after running the criterion and confirming
   the underlying command succeeded while the assertion still failed. Name the affected
   check ids, emit it alongside your normal `RESULT` line, state the reasoning in
   `FAILURE_DETAIL`, and emit it even when `RESULT: SUCCESS`. Do not use it for a
   criterion you merely failed to satisfy.

# Active Commander guidance

## Commander decisions

Apply these decisions as constraints; do not recreate them as questions.

### analyze-display_name
- Title: Display Name
- Decision: ReadingList
- Severity: material
- Blueprint: ARCHITECTURE.md

### analyze-short_description
- Title: Short Description
- Decision: A web application for maintaining an ordered list of books to read.
- Severity: material
- Blueprint: ARCHITECTURE.md

