mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
* [805e525a] refactor(api): extract _require_ceo/_to_response/_status_value from 11 route files Move the per-file local _require_ceo wrapper, task->Response converters, and status-value mappers out of board_programs.py, coroner.py, dogfood.py, github_app.py, mirror.py, periscope.py, pest_control.py, scales.py, sentinel.py, spackle.py, and telegram.py routes. _require_ceo call sites now call the shared require_ceo_role directly; pure converters land in each domain's roboco/api/schemas/*.py module (task_to_<domain>_response, <domain>_status_value), mirroring the roadmap.py pattern; board_programs.py's engine-backed _to_response becomes BoardProgramEngine.to_response, mirroring release_proposal.py's task_to_proposal_response precedent. Placement-only, no route/schema/behavior changes. * [805e525a] docs(api): document Batch C route-helper relocation in api-routes-schemas map --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech>
104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
"""Dogfood (Board Program) engine API — the CEO approves/rejects items
|
|
within a held friction-fix cycle. CEO-only throughout. Approving an item
|
|
materializes it as a BACKLOG task; nothing here starts it — normal PM
|
|
activation takes it from there. Mirrors ``roboco.api.routes.spackle``.
|
|
"""
|
|
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, HTTPException, status
|
|
|
|
from roboco.api.deps import CurrentAgentContext, DbSession, require_ceo_role
|
|
from roboco.api.schemas.dogfood import (
|
|
DogfoodCycleResponse,
|
|
FrictionFixItemActionResponse,
|
|
FrictionFixRejectRequest,
|
|
task_to_dogfood_cycle_response,
|
|
)
|
|
from roboco.foundation.policy.content import markers
|
|
from roboco.security import guard_deco
|
|
from roboco.services.dogfood_service import get_dogfood_service
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/cycles", response_model=list[DogfoodCycleResponse])
|
|
async def list_dogfood_cycles(
|
|
db: DbSession, agent: CurrentAgentContext
|
|
) -> list[DogfoodCycleResponse]:
|
|
"""Every open dogfood cycle already authored by the Product Owner.
|
|
|
|
A cycle the PO hasn't authored yet (no items drafted) is omitted — there
|
|
is nothing for the CEO to review until ``propose_friction_fixes`` lands.
|
|
"""
|
|
require_ceo_role(agent.role, action="view or act on the dogfood queue")
|
|
tasks = await get_dogfood_service(db).list_open_cycles()
|
|
return [
|
|
task_to_dogfood_cycle_response(t)
|
|
for t in tasks
|
|
if markers.get_friction_fixes(t)
|
|
]
|
|
|
|
|
|
@router.post(
|
|
"/cycles/{task_id}/items/{item_id}/approve",
|
|
response_model=FrictionFixItemActionResponse,
|
|
)
|
|
@guard_deco.rate_limit(requests=30, window=60)
|
|
@guard_deco.block_clouds()
|
|
async def approve_friction_fix_item(
|
|
task_id: UUID,
|
|
item_id: str,
|
|
db: DbSession,
|
|
agent: CurrentAgentContext,
|
|
) -> FrictionFixItemActionResponse:
|
|
"""Materialize one proposed item as a BACKLOG task (idempotent)."""
|
|
require_ceo_role(agent.role, action="view or act on the dogfood queue")
|
|
result = await get_dogfood_service(db).approve_item(
|
|
task_id, item_id, created_by=agent.agent_id
|
|
)
|
|
if result is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="No such open dogfood item",
|
|
)
|
|
await db.commit()
|
|
return FrictionFixItemActionResponse(
|
|
status=result.status,
|
|
item_id=result.item_id,
|
|
materialized_task_id=result.materialized_task_id,
|
|
detail=result.detail,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/cycles/{task_id}/items/{item_id}/reject",
|
|
response_model=FrictionFixItemActionResponse,
|
|
)
|
|
@guard_deco.rate_limit(requests=30, window=60)
|
|
@guard_deco.block_clouds()
|
|
@guard_deco.content_type_filter(["application/json"])
|
|
@guard_deco.honeypot_detection(["email", "phone", "website"])
|
|
async def reject_friction_fix_item(
|
|
task_id: UUID,
|
|
item_id: str,
|
|
data: FrictionFixRejectRequest,
|
|
db: DbSession,
|
|
agent: CurrentAgentContext,
|
|
) -> FrictionFixItemActionResponse:
|
|
"""Reject one proposed item with a reason (idempotent)."""
|
|
require_ceo_role(agent.role, action="view or act on the dogfood queue")
|
|
result = await get_dogfood_service(db).reject_item(task_id, item_id, data.reason)
|
|
if result is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="No such open dogfood item",
|
|
)
|
|
await db.commit()
|
|
return FrictionFixItemActionResponse(
|
|
status=result.status,
|
|
item_id=result.item_id,
|
|
materialized_task_id=result.materialized_task_id,
|
|
detail=result.detail,
|
|
)
|