akif/arclet-boards

Arclet Boards — private Kanban workspaces with AppID, DataK3, shared web and MCP actions.

A remix template · 1 remixes · Remix akif/arclet-boards

app/application/contracts.py

"""One validated command contract shared by the HTTP and MCP doors."""
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field


class Input(BaseModel):
    model_config = ConfigDict(extra="forbid", strict=True)


class Empty(Input):
    pass


class WorkspaceIn(Input):
    title: str


class WorkspaceId(Input):
    workspace_id: str


class BoardIn(Input):
    title: str
    workspace_id: str
    description: str = ""
    color: str = "indigo"


class BoardId(Input):
    board_id: str


class BoardEdit(BoardId):
    title: str
    description: str = ""
    color: str = "indigo"
    version: int = Field(ge=1)


class ListIn(BoardId):
    title: str
    is_done: bool = False


class ListEdit(Input):
    list_id: str
    title: str
    is_done: bool
    version: int = Field(ge=1)


class ListOrder(BoardId):
    list_ids: list[str] = Field(max_length=100)
    version: int = Field(ge=1)


class CardIn(BoardId):
    list_id: str
    title: str
    description: str = ""
    assignee: str | None = None
    due_date: str | None = None


class CardId(Input):
    card_id: str


class Label(Input):
    name: str
    color: str


class CardEdit(CardId):
    title: str
    description: str
    assignee: str | None
    due_date: str | None
    labels: list[Label] = Field(max_length=12)
    version: int = Field(ge=1)


class CardMove(CardId):
    list_id: str
    index: int = Field(ge=0, le=10000)
    version: int = Field(ge=1)
    board_version: int = Field(ge=1)


class CardAssign(CardId):
    assignee: str | None
    version: int = Field(ge=1)


class CardDue(CardId):
    due_date: str | None
    version: int = Field(ge=1)


class MemberIn(BoardId):
    email: str
    role: Literal["member", "viewer"] = "member"


class MemberRemove(BoardId):
    email: str


class WorkspaceMember(WorkspaceId):
    email: str
    role: Literal["member", "viewer"] = "member"


class ChecklistIn(CardId):
    title: str
    version: int = Field(ge=1)


class ChecklistEdit(Input):
    item_id: str
    completed: bool
    version: int = Field(ge=1)


class DependencyIn(CardId):
    depends_on_id: str
    version: int = Field(ge=1)


class ApplyChecklist(CardId):
    items: list[str] = Field(min_length=1, max_length=8)
    version: int = Field(ge=1)


class IndexBoard(BoardId):
    version: int = Field(ge=1)


READS = {
    "list_workspaces": Empty, "get_workspace": WorkspaceId,
    "list_boards": Empty, "get_board": BoardId, "get_card": CardId,
    "list_my_cards": Empty, "list_overdue": Empty, "get_card_activity": CardId,
    "list_members": BoardId,
    "get_blocking_chain": CardId, "suggest_checklist": CardId,
    "daily_summary": BoardId, "find_similar_cards": CardId,
}
WRITES = {
    "create_workspace": WorkspaceIn, "invite_workspace_member": WorkspaceMember,
    "create_board": BoardIn, "update_board": BoardEdit,
    "create_list": ListIn, "update_list": ListEdit, "reorder_lists": ListOrder,
    "add_card": CardIn, "update_card": CardEdit, "move_card": CardMove,
    "assign_card": CardAssign, "set_due_date": CardDue,
    "invite_member": MemberIn, "change_member_role": MemberIn,
    "remove_member": MemberRemove, "add_checklist_item": ChecklistIn,
    "complete_checklist_item": ChecklistEdit,
    "add_dependency": DependencyIn, "remove_dependency": DependencyIn,
    "apply_checklist": ApplyChecklist, "refresh_semantic_index": IndexBoard,
}

AI_ACTIONS = {"suggest_checklist", "daily_summary", "refresh_semantic_index"}