This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/timmy/approvals.py

172 lines
5.8 KiB
Python

"""Approval item management — the governance layer for autonomous Timmy actions.
The GOLDEN_TIMMY constant is the single source of truth for whether Timmy
may act autonomously. All features that want to take action must:
1. Create an ApprovalItem
2. Check GOLDEN_TIMMY
3. If True → wait for owner approval before executing
4. If False → log the action and proceed (Dark Timmy mode)
Default is always True. The owner changes this intentionally.
"""
import sqlite3
import uuid
from collections.abc import Generator
from contextlib import closing, contextmanager
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from pathlib import Path
# ---------------------------------------------------------------------------
# GOLDEN TIMMY RULE
# ---------------------------------------------------------------------------
GOLDEN_TIMMY = True
# When True: no autonomous action executes without an approved ApprovalItem.
# When False: Dark Timmy mode — Timmy may act on his own judgment.
# Default is always True. Owner changes this intentionally.
# ---------------------------------------------------------------------------
# Persistence
# ---------------------------------------------------------------------------
_DEFAULT_DB = Path.home() / ".timmy" / "approvals.db"
_EXPIRY_DAYS = 7
@dataclass
class ApprovalItem:
id: str
title: str
description: str
proposed_action: str # what Timmy wants to do
impact: str # "low" | "medium" | "high"
created_at: datetime
status: str # "pending" | "approved" | "rejected"
@contextmanager
def _get_conn(db_path: Path = _DEFAULT_DB) -> Generator[sqlite3.Connection, None, None]:
db_path.parent.mkdir(parents=True, exist_ok=True)
with closing(sqlite3.connect(str(db_path))) as conn:
conn.row_factory = sqlite3.Row
conn.execute("""
CREATE TABLE IF NOT EXISTS approval_items (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
proposed_action TEXT NOT NULL,
impact TEXT NOT NULL DEFAULT 'low',
created_at TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
)
""")
conn.commit()
yield conn
def _row_to_item(row: sqlite3.Row) -> ApprovalItem:
return ApprovalItem(
id=row["id"],
title=row["title"],
description=row["description"],
proposed_action=row["proposed_action"],
impact=row["impact"],
created_at=datetime.fromisoformat(row["created_at"]),
status=row["status"],
)
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def create_item(
title: str,
description: str,
proposed_action: str,
impact: str = "low",
db_path: Path = _DEFAULT_DB,
) -> ApprovalItem:
"""Create and persist a new approval item."""
item = ApprovalItem(
id=str(uuid.uuid4()),
title=title,
description=description,
proposed_action=proposed_action,
impact=impact,
created_at=datetime.now(UTC),
status="pending",
)
with _get_conn(db_path) as conn:
conn.execute(
"""
INSERT INTO approval_items
(id, title, description, proposed_action, impact, created_at, status)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
item.id,
item.title,
item.description,
item.proposed_action,
item.impact,
item.created_at.isoformat(),
item.status,
),
)
conn.commit()
return item
def list_pending(db_path: Path = _DEFAULT_DB) -> list[ApprovalItem]:
"""Return all pending approval items, newest first."""
with _get_conn(db_path) as conn:
rows = conn.execute(
"SELECT * FROM approval_items WHERE status = 'pending' ORDER BY created_at DESC"
).fetchall()
return [_row_to_item(r) for r in rows]
def list_all(db_path: Path = _DEFAULT_DB) -> list[ApprovalItem]:
"""Return all approval items regardless of status, newest first."""
with _get_conn(db_path) as conn:
rows = conn.execute("SELECT * FROM approval_items ORDER BY created_at DESC").fetchall()
return [_row_to_item(r) for r in rows]
def get_item(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
with _get_conn(db_path) as conn:
row = conn.execute("SELECT * FROM approval_items WHERE id = ?", (item_id,)).fetchone()
return _row_to_item(row) if row else None
def approve(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
"""Mark an approval item as approved."""
with _get_conn(db_path) as conn:
conn.execute("UPDATE approval_items SET status = 'approved' WHERE id = ?", (item_id,))
conn.commit()
return get_item(item_id, db_path)
def reject(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
"""Mark an approval item as rejected."""
with _get_conn(db_path) as conn:
conn.execute("UPDATE approval_items SET status = 'rejected' WHERE id = ?", (item_id,))
conn.commit()
return get_item(item_id, db_path)
def expire_old(db_path: Path = _DEFAULT_DB) -> int:
"""Auto-expire pending items older than EXPIRY_DAYS. Returns count removed."""
cutoff = (datetime.now(UTC) - timedelta(days=_EXPIRY_DAYS)).isoformat()
with _get_conn(db_path) as conn:
cursor = conn.execute(
"DELETE FROM approval_items WHERE status = 'pending' AND created_at < ?",
(cutoff,),
)
conn.commit()
count = cursor.rowcount
return count