theaio/theaio-boards

Laneway, team task boards you can run from Claude or ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 0 remixes · Remix theaio/theaio-boards

app/boards/application/ports.py

"""What the use cases need from the outside world. infrastructure/ provides it; tests fake it."""
from datetime import datetime
from typing import Optional, Protocol

from boards.domain.model import Board, Card


class BoardRepository(Protocol):
    # boards, members and lists (one aggregate)
    def get(self, board_id: str) -> Optional[Board]: ...
    def boards_for(self, email: str) -> list: ...
    def save_board(self, board: Board) -> None: ...
    def remove_member(self, board_id: str, email: str) -> None: ...
    def log(self, board_id: str, card_id: Optional[str], actor: str, action: str) -> None: ...

    # cards
    def get_card(self, card_id: str) -> Optional[Card]: ...
    def card_by_number(self, board_id: str, number: int) -> Optional[Card]: ...
    def cards_on(self, board_id: str) -> list: ...
    def max_card_number(self, board_id: str) -> int: ...
    def insert_card(self, card: Card) -> bool: ...  # plain INSERT claiming card.id; False if taken
    def save_card(self, card: Card) -> None: ...

    # history: append-only
    def record(self, entries: list) -> None: ...
    def history(self, card_id: str) -> list: ...
    def board_history(self, board_id: str, since: datetime) -> list: ...

    # "blocked by" links: (card_id, blocked_by) pairs
    def links_on(self, board_id: str) -> list: ...
    def add_link(self, board_id: str, card_id: str, blocked_by: str) -> None: ...
    def remove_link(self, card_id: str, blocked_by: str) -> None: ...

    # meaning search: one vector per card, tagged with a digest of the text it was made from
    def embedding_digests(self, board_ids: list) -> dict: ...
    def save_embedding(self, card: Card, digest: str, vector: list) -> None: ...
    def vector_of(self, card_id: str) -> Optional[list]: ...
    def nearest(self, board_ids: list, vector: list, exclude_id: str, limit: int) -> list: ...


class Assistant(Protocol):
    """Dodil Models behind one port, so tests use a fake and the rules never wait on a model."""
    def checklist(self, card_title: str) -> list: ...
    def embed(self, texts: list) -> list: ...
    def summarize(self, facts: str) -> str: ...