Replace repository with DuckLM runtime
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
import aiosqlite
|
||||
from pydantic import BaseModel
|
||||
|
||||
from duck_core.tasks.store import utc_now
|
||||
|
||||
|
||||
class Approval(BaseModel):
|
||||
id: int | None = None
|
||||
approval_id: str
|
||||
task_id: str
|
||||
action_hash: str
|
||||
normalized_action: dict[str, Any]
|
||||
status: str
|
||||
decision: str | None = None
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
def normalize_action(action: dict[str, Any]) -> str:
|
||||
return json.dumps(action, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def action_hash(action: dict[str, Any]) -> str:
|
||||
return hashlib.sha256(normalize_action(action).encode()).hexdigest()
|
||||
|
||||
|
||||
class ApprovalService:
|
||||
def __init__(self, db_path: str):
|
||||
self.db_path = Path(db_path)
|
||||
|
||||
async def init(self) -> None:
|
||||
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute(
|
||||
"""
|
||||
create table if not exists approvals (
|
||||
id integer primary key autoincrement,
|
||||
approval_id text not null unique,
|
||||
task_id text not null,
|
||||
action_hash text not null,
|
||||
normalized_action_json text not null,
|
||||
status text not null,
|
||||
decision text,
|
||||
created_at text not null,
|
||||
updated_at text not null
|
||||
)
|
||||
"""
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async def create_pending(self, task_id: str, action: dict[str, Any]) -> Approval:
|
||||
await self.init()
|
||||
now = utc_now()
|
||||
approval_id = f"approval_{uuid4().hex[:12]}"
|
||||
normalized = normalize_action(action)
|
||||
digest = action_hash(action)
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute(
|
||||
"""
|
||||
insert into approvals(
|
||||
approval_id, task_id, action_hash, normalized_action_json,
|
||||
status, created_at, updated_at
|
||||
) values (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(approval_id, task_id, digest, normalized, "pending", now, now),
|
||||
)
|
||||
await db.commit()
|
||||
row_id = cursor.lastrowid
|
||||
return Approval(
|
||||
id=row_id,
|
||||
approval_id=approval_id,
|
||||
task_id=task_id,
|
||||
action_hash=digest,
|
||||
normalized_action=action,
|
||||
status="pending",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
|
||||
async def pending(self) -> list[Approval]:
|
||||
await self.init()
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute(
|
||||
"select * from approvals where status = 'pending' order by created_at"
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [self._row_to_approval(row) for row in rows]
|
||||
|
||||
async def allow_once(self, approval_id: str) -> None:
|
||||
await self._decide(approval_id, "resolved", "allow_once")
|
||||
|
||||
async def allow_forever(self, approval_id: str) -> None:
|
||||
await self._decide(approval_id, "allowed_forever", "allow_forever")
|
||||
|
||||
async def deny(self, approval_id: str) -> None:
|
||||
await self._decide(approval_id, "resolved", "deny")
|
||||
|
||||
async def is_allowed_forever(self, action: dict[str, Any]) -> bool:
|
||||
await self.init()
|
||||
digest = action_hash(action)
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute(
|
||||
"""
|
||||
select 1 from approvals
|
||||
where action_hash = ? and status = 'allowed_forever'
|
||||
limit 1
|
||||
""",
|
||||
(digest,),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row is not None
|
||||
|
||||
async def _decide(self, approval_id: str, status: str, decision: str) -> None:
|
||||
await self.init()
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute(
|
||||
"""
|
||||
update approvals set status = ?, decision = ?, updated_at = ?
|
||||
where approval_id = ?
|
||||
""",
|
||||
(status, decision, utc_now(), approval_id),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
def _row_to_approval(self, row: aiosqlite.Row) -> Approval:
|
||||
return Approval(
|
||||
id=row["id"],
|
||||
approval_id=row["approval_id"],
|
||||
task_id=row["task_id"],
|
||||
action_hash=row["action_hash"],
|
||||
normalized_action=json.loads(row["normalized_action_json"]),
|
||||
status=row["status"],
|
||||
decision=row["decision"],
|
||||
created_at=row["created_at"],
|
||||
updated_at=row["updated_at"],
|
||||
)
|
||||
Reference in New Issue
Block a user