- Migration b4c5d6e7f8a9: adds needs_human (bool) + intervention_note (text) to tasks - API: needs_human filter on GET /tasks/; 422 if flagged without note - 3 MCP tools: flag_for_human, clear_human_flag, list_human_interventions - Dashboard: interventions.md with amber cards and "Mark done" button - Policy router + workstream DoD policy (workstream-dod.md) - Workstream lifecycle docs page + workplan CUST-WP-0010 - CLAUDE.md: add step 4 (run fix-consistency after workplan writes) - consistency_check.py: promote C-11 unlinked tasks from INFO to WARN Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
42 lines
1.1 KiB
Python
42 lines
1.1 KiB
Python
import re
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
POLICY_DIR = Path(__file__).parent.parent.parent / "policies"
|
|
_VALID_NAME = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
|
|
|
|
router = APIRouter(prefix="/policy", tags=["policy"])
|
|
|
|
|
|
class PolicyRead(BaseModel):
|
|
name: str
|
|
content: str
|
|
|
|
|
|
class PolicyUpdate(BaseModel):
|
|
content: str
|
|
|
|
|
|
def _policy_path(name: str) -> Path:
|
|
if not _VALID_NAME.match(name):
|
|
raise HTTPException(status_code=400, detail="Invalid policy name")
|
|
path = POLICY_DIR / f"{name}.md"
|
|
if not path.exists():
|
|
raise HTTPException(status_code=404, detail=f"Policy '{name}' not found")
|
|
return path
|
|
|
|
|
|
@router.get("/{name}", response_model=PolicyRead)
|
|
def get_policy(name: str) -> PolicyRead:
|
|
path = _policy_path(name)
|
|
return PolicyRead(name=name, content=path.read_text())
|
|
|
|
|
|
@router.put("/{name}", response_model=PolicyRead)
|
|
def update_policy(name: str, body: PolicyUpdate) -> PolicyRead:
|
|
path = _policy_path(name)
|
|
path.write_text(body.content)
|
|
return PolicyRead(name=name, content=body.content)
|