feat(F): F-PERM permissions, F-APPR approval, F-AIUSE metadata, F-TRANS transparency, F-DATA-POL data policy, F-OVERSIGHT decision record, F-DRY dry-run, F-AUDIT audit log
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- F-PERM: app/ai/agent_permissions.py (230 lines) — AgentPermissionContext, resolve_agent_permissions(), filter_visible_agents(), check_agent_execute_permission(), optimistic locking - F-APPR: app/core/approval.py (160 lines) + app/routes/approvals.py (305 lines) + migration 0123 — ApprovalRequest model, CRUD API, approve/reject/expire - F-AIUSE: app/ai/ai_use_case.py (156 lines) — AIUseCaseMetadata Pydantic model, validate_ai_use_case() - F-TRANS: app/ai/transparency.py (60 lines) — mark_as_ai_generated(), is_ai_participant() - F-DATA-POL: app/ai/data_policy.py (210 lines) — enforce_data_policy() with SENSITIVE_FIELDS + provider compliance - F-OVERSIGHT: app/ai/oversight.py (108 lines) — DecisionRecord, create_decision_record() - F-DRY: agent_loop.py updated with dry_run parameter - F-AUDIT: agent_loop.py updated with audit log for tool calls - agent_routes.py: AI use case metadata endpoints added - main.py: approval routes registered - All Python compile checks pass
This commit is contained in:
@@ -11,6 +11,7 @@ from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db
|
||||
@@ -258,6 +259,93 @@ async def update_agent(
|
||||
return _agent_to_response(agent)
|
||||
|
||||
|
||||
# ─── AI Use-Case Metadata ───
|
||||
|
||||
|
||||
class AIUseCaseMetadataUpdate(BaseModel):
|
||||
"""Update AI use-case metadata for an agent."""
|
||||
|
||||
intended_purpose: str | None = None
|
||||
owner: str | None = None
|
||||
data_categories: list[str] | None = None
|
||||
allowed_providers: list[str] | None = None
|
||||
allowed_models: list[str] | None = None
|
||||
allowed_actions: list[str] | None = None
|
||||
oversight_policy: str | None = None
|
||||
risk_class: str | None = None
|
||||
human_review_required: bool | None = None
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{agent_id}/ai-use-case",
|
||||
dependencies=[Depends(require_permission("agents:read"))],
|
||||
)
|
||||
async def get_ai_use_case(
|
||||
agent_id: str,
|
||||
current_user: dict[str, Any] = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get AI use-case metadata for an agent."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
if agent is None:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"agent_id": agent_id, "ai_use_case_metadata": agent.ai_use_case_metadata or {}}
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{agent_id}/ai-use-case",
|
||||
dependencies=[Depends(require_permission("agents:write"))],
|
||||
)
|
||||
async def update_ai_use_case(
|
||||
agent_id: str,
|
||||
data: AIUseCaseMetadataUpdate,
|
||||
current_user: dict[str, Any] = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update AI use-case metadata for an agent."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
if agent is None:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
# Merge with existing metadata (partial update).
|
||||
current = dict(agent.ai_use_case_metadata or {})
|
||||
updates = data.model_dump(exclude_none=True)
|
||||
current.update(updates)
|
||||
|
||||
# Validate the merged metadata against the agent config.
|
||||
from app.ai.ai_use_case import AIUseCaseMetadata, validate_ai_use_case
|
||||
|
||||
try:
|
||||
metadata = AIUseCaseMetadata(**current)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid AI use-case metadata: {e}") from None
|
||||
warnings = validate_ai_use_case(metadata, agent)
|
||||
|
||||
updated = await AgentService.update(
|
||||
db, tenant_id, aid, {"ai_use_case_metadata": current}, user_id=user_id
|
||||
)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {
|
||||
"agent_id": agent_id,
|
||||
"ai_use_case_metadata": current,
|
||||
"warnings": warnings,
|
||||
}
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{agent_id}",
|
||||
dependencies=[Depends(require_permission("agents:delete"))],
|
||||
|
||||
Reference in New Issue
Block a user