G0~G8 성과·동맹 측정 OS 작업 일괄 고정

8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본
폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을
git 이력으로 고정하는 것이 목적이다.

- contracts/routes/services: measurement, outcome_trajectory, rupture_repair,
  deliberate_practice, calibration_transfer, supervision_research,
  multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트
- infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행)
- apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가
- docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG
- scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트

engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
This commit is contained in:
Yun Chan 2026-08-08 01:30:53 +09:00
parent 93dd8f82d7
commit 16e791e044
390 changed files with 243188 additions and 499 deletions

View file

@ -0,0 +1,809 @@
"""Standalone secure HTTP boundary for G8 Continuous Improvement OS."""
from __future__ import annotations
import secrets
from collections.abc import AsyncIterator
from datetime import datetime
from typing import Annotated, Literal
from uuid import UUID
import asyncpg
from fastapi import APIRouter, Depends, Header, HTTPException, status
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from .. import db
from ..config import Settings, get_settings
from ..contracts.continuous_improvement import (
AgenticReleaseManifest,
ContentBenchmarkQualification,
ContentSourceArtifact,
GeneratedContentDraft,
IndependentRedTeamReview,
ModelCalibrationSnapshot,
OperationalIncident,
)
from ..deps import Principal, Role, require_role
from ..engine_client import engine_client
from ..services import continuous_improvement_store
from ..services import continuous_improvement_agentic
router = APIRouter(tags=["continuous-improvement"])
INTERNAL_TOKEN_HEADER = "X-Vignette-Continuous-Improvement-Token"
MIN_INTERNAL_TOKEN_LENGTH = 32
SyntheticDataClassification = Literal["synthetic_replay_red_team_coverage_drift"]
async def _research_db_provider() -> AsyncIterator[asyncpg.Connection]:
async with db.acquire(ai_view="research", ai_context=True) as conn:
yield conn
def _authenticate_internal(settings: Settings, presented_token: str | None) -> None:
configured_token = settings.continuous_improvement_internal_token.get_secret_value()
if len(configured_token) < MIN_INTERNAL_TOKEN_LENGTH:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="continuous improvement automation is unavailable",
)
if presented_token is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="internal authentication required",
)
if not secrets.compare_digest(presented_token, configured_token):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="internal authentication failed",
)
async def continuous_improvement_internal_db(
settings: Annotated[Settings, Depends(get_settings)],
presented_token: Annotated[str | None, Header(alias=INTERNAL_TOKEN_HEADER)] = None,
) -> AsyncIterator[asyncpg.Connection]:
"""Fail closed before acquiring the research-view database connection."""
_authenticate_internal(settings, presented_token)
async for conn in _research_db_provider():
yield conn
ResearchDB = Annotated[asyncpg.Connection, Depends(continuous_improvement_internal_db)]
AdminPrincipal = Annotated[Principal, Depends(require_role(Role.ADMIN))]
async def continuous_improvement_admin_db(
principal: AdminPrincipal,
) -> AsyncIterator[asyncpg.Connection]:
"""Acquire DB state with the effective admin role selected by the role gate."""
async with db.acquire(
role=Role.ADMIN.value,
user_id=principal.user_id,
cohort_ids=principal.cohort_ids,
) as conn:
yield conn
AdminDB = Annotated[asyncpg.Connection, Depends(continuous_improvement_admin_db)]
class ContentPipelineRequest(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())
submission_id: UUID
pipeline_id: UUID
benchmark_record_id: UUID
qualification_id: UUID
data_classification: SyntheticDataClassification
draft: GeneratedContentDraft
sources: list[ContentSourceArtifact] = Field(min_length=1, max_length=100)
reviews: list[IndependentRedTeamReview] = Field(min_length=2, max_length=20)
benchmark: ContentBenchmarkQualification
class ContentPipelineResponse(BaseModel):
submission_id: UUID
pipeline_id: UUID
qualification_id: UUID
candidate_catalog_entry_id: str
state: Literal["pending_human_approval"]
human_approval_required: Literal[True]
catalog_promoted: Literal[False]
idempotent_replay: bool
clinical_claim_allowed: Literal[False] = False
class AgenticContentPipelineRequest(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())
submission_id: UUID
pipeline_id: UUID
benchmark_record_id: UUID
qualification_id: UUID
data_classification: SyntheticDataClassification
source_packs: list[continuous_improvement_agentic.AgenticSourcePack] = Field(
min_length=1, max_length=20
)
content_kind: Literal["case", "rupture", "practice", "benchmark"]
difficulty_level: int = Field(ge=1, le=5)
variant_count: int = Field(default=3, ge=3, le=12)
prompt_version: str = Field(default="1.0.0", min_length=1, max_length=80)
@model_validator(mode="after")
def unique_source_packs(self) -> "AgenticContentPipelineRequest":
source_ids = [item.artifact.source_id for item in self.source_packs]
if len(source_ids) != len(set(source_ids)):
raise ValueError("agentic source pack ids must be unique")
return self
class AgenticContentPipelineResponse(ContentPipelineResponse):
draft_id: str
benchmark_id: str
red_team_review_count: int = Field(ge=2)
benchmark_variant_count: int = Field(ge=3)
agent_calls_executed: int = Field(ge=0)
trigger_kind: Literal["source_pack", "operational_incident"]
class IncidentAdversarialPipelineRequest(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())
submission_id: UUID
pipeline_id: UUID
benchmark_record_id: UUID
qualification_id: UUID
data_classification: SyntheticDataClassification
difficulty_level: int = Field(default=5, ge=1, le=5)
variant_count: int = Field(default=3, ge=3, le=12)
prompt_version: str = Field(default="1.0.0", min_length=1, max_length=80)
class GateArtifact(BaseModel):
model_config = ConfigDict(extra="forbid")
artifact_record_id: UUID
artifact_id: str = Field(min_length=1, max_length=180)
content_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
provenance_uri: str = Field(pattern=r"^(repo|db|audit)://[a-zA-Z0-9_./:-]+$")
class CompleteGateArtifacts(BaseModel):
model_config = ConfigDict(extra="forbid")
baseline: GateArtifact
threshold: GateArtifact
provenance: list[GateArtifact] = Field(min_length=1, max_length=100)
rollback: GateArtifact
@model_validator(mode="after")
def artifact_ids_are_unique(self) -> "CompleteGateArtifacts":
values = [
self.baseline.artifact_record_id,
self.threshold.artifact_record_id,
*(item.artifact_record_id for item in self.provenance),
self.rollback.artifact_record_id,
]
if len(values) != len(set(values)):
raise ValueError("gate artifact UUIDs must be unique")
return self
class ModelChangeGateRequest(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())
submission_id: UUID
gate_id: UUID
baseline_snapshot_record_id: UUID
candidate_snapshot_record_id: UUID
data_classification: SyntheticDataClassification
baseline: ModelCalibrationSnapshot
candidate: ModelCalibrationSnapshot
artifacts: CompleteGateArtifacts
@model_validator(mode="after")
def baseline_and_candidate_are_distinct(self) -> "ModelChangeGateRequest":
if self.baseline.snapshot_id == self.candidate.snapshot_id:
raise ValueError("baseline and candidate snapshots must be distinct")
if self.baseline_snapshot_record_id == self.candidate_snapshot_record_id:
raise ValueError("baseline and candidate record UUIDs must be distinct")
return self
class ModelChangeGateResponse(BaseModel):
submission_id: UUID
gate_id: UUID
gate_decision: Literal["promote", "rollback", "quarantine"]
state: Literal["pending_human_approval"]
human_approval_required: Literal[True]
promotion_executed: Literal[False]
idempotent_replay: bool
class ReleaseGateRequest(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())
submission_id: UUID
gate_id: UUID
data_classification: SyntheticDataClassification
manifest: AgenticReleaseManifest
artifacts: CompleteGateArtifacts
class ReleaseGateResponse(BaseModel):
submission_id: UUID
gate_id: UUID
qualified: bool
state: Literal["pending_human_approval"]
human_approval_required: Literal[True]
promotion_executed: Literal[False]
idempotent_replay: bool
class IncidentDagRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
submission_id: UUID
incident_record_id: UUID
data_classification: SyntheticDataClassification
incident: OperationalIncident
class IncidentDagResponse(BaseModel):
submission_id: UUID
incident_record_id: UUID
node_count: Literal[4]
idempotent_replay: bool
pii_included: Literal[False] = False
class HumanApprovalRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
submission_id: UUID
approval_event_id: UUID
effect_record_id: UUID
target_kind: Literal["content_qualification", "model_change_gate", "release_gate"]
target_id: UUID
decision: Literal[
"approve_content",
"approve_promotion",
"authorize_rollback",
"reject",
"keep_quarantine",
]
reason_code: str = Field(min_length=1, max_length=180)
evidence_refs: list[str] = Field(min_length=1, max_length=100)
@field_validator("evidence_refs")
@classmethod
def evidence_refs_are_unique(cls, value: list[str]) -> list[str]:
if len(value) != len(set(value)):
raise ValueError("approval evidence refs must be unique")
return value
class HumanApprovalResponse(BaseModel):
submission_id: UUID
approval_event_id: UUID
target_kind: str
target_id: UUID
decision: str
effect_record_id: UUID
idempotent_replay: bool
class MonitorEventRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
submission_id: UUID
lifecycle_event_id: UUID
data_classification: SyntheticDataClassification
target_kind: Literal["model_change_gate", "release_gate"]
target_id: UUID
event_status: Literal[
"healthy", "drift_detected", "rollback_recommended", "rollback_verified"
]
evidence_refs: list[str] = Field(min_length=1, max_length=100)
class MonitorEventResponse(BaseModel):
submission_id: UUID
lifecycle_event_id: UUID
event_status: str
idempotent_replay: bool
class CatalogGroundedClaim(BaseModel):
model_config = ConfigDict(extra="forbid")
claim: str = Field(min_length=1, max_length=600)
source_ref: str = Field(min_length=1, max_length=180)
class CatalogVisiblePayload(BaseModel):
"""Explicit allowlist for content that may cross the approved catalog boundary."""
model_config = ConfigDict(extra="forbid")
title: str = Field(min_length=1, max_length=180)
synthetic_profile: str = Field(min_length=1, max_length=1200)
scenario: str = Field(min_length=1, max_length=6000)
rupture_or_challenge: str = Field(min_length=1, max_length=2400)
learner_task: str = Field(min_length=1, max_length=2000)
success_criteria: list[str] = Field(min_length=1, max_length=10)
source_refs: list[str] = Field(min_length=1, max_length=100)
grounded_claims: list[CatalogGroundedClaim] = Field(min_length=1, max_length=20)
class ContentQualificationView(BaseModel):
qualification_id: UUID
pipeline_id: UUID
catalog_entry_id: str
payload_sha256: str
content_kind: Literal["case", "rupture", "practice", "benchmark"]
difficulty_level: int = Field(ge=1, le=5)
synthetic_identity_id: str
source_count: int = Field(ge=1)
red_team_review_count: int = Field(ge=2)
benchmark_variant_count: int = Field(ge=3)
benchmark_pass_rate: float = Field(ge=0.85, le=1.0)
source_provenance_uris: list[str] = Field(min_length=1)
draft_payload: CatalogVisiblePayload | None = None
gate_state: Literal["pending_human_approval"]
created_at: datetime
class ModelChangeGateView(BaseModel):
gate_id: UUID
gate_decision: Literal["promote", "rollback", "quarantine"]
reasons: list[str]
state: Literal["pending_human_approval"]
created_at: datetime
class ReleaseGateView(BaseModel):
gate_id: UUID
release_id: str
qualified: bool
state: Literal["pending_human_approval"]
created_at: datetime
class GateArtifactView(BaseModel):
artifact_record_id: UUID
owner_kind: Literal["model_change_gate", "release_gate"]
owner_id: UUID
artifact_kind: Literal["baseline", "threshold", "provenance", "rollback"]
artifact_id: str
content_sha256: str
provenance_uri: str
created_at: datetime
class HumanApprovalView(BaseModel):
approval_event_id: UUID
target_kind: Literal["content_qualification", "model_change_gate", "release_gate"]
target_id: UUID
decision: Literal[
"approve_content",
"approve_promotion",
"authorize_rollback",
"reject",
"keep_quarantine",
]
reason_code: str
evidence_refs: list[str]
created_at: datetime
class CatalogEntryView(BaseModel):
catalog_record_id: UUID
qualification_id: UUID
catalog_entry_id: str
status: Literal["approved"]
clinical_claim_allowed: Literal[False]
created_at: datetime
class ApprovedCatalogConsumerEntry(BaseModel):
catalog_record_id: UUID
qualification_id: UUID
catalog_entry_id: str
payload_sha256: str
content_kind: Literal["case", "rupture", "practice", "benchmark"]
difficulty_level: int = Field(ge=1, le=5)
synthetic_identity_id: str
source_provenance_uris: list[str] = Field(min_length=1)
payload: CatalogVisiblePayload
status: Literal["approved"]
clinical_claim_allowed: Literal[False]
approved_at: datetime
class ApprovedCatalogConsumerResponse(BaseModel):
entries: list[ApprovedCatalogConsumerEntry]
data_classification: SyntheticDataClassification
human_approval_required: Literal[True] = True
raw_transcript_included: Literal[False] = False
pii_included: Literal[False] = False
clinical_claim_allowed: Literal[False] = False
class LifecycleEventView(BaseModel):
lifecycle_event_id: UUID
target_kind: Literal["model_change_gate", "release_gate"]
target_id: UUID
event_type: Literal["promotion", "rollback", "monitor"]
event_status: Literal[
"approved",
"requested",
"executed",
"failed",
"healthy",
"drift_detected",
"rollback_recommended",
"rollback_verified",
]
approval_event_id: UUID | None = None
artifact_record_id: UUID | None = None
evidence_refs: list[str]
executor_receipt_id: str | None = None
executor_evidence_refs: list[str] | None = None
created_at: datetime
@model_validator(mode="after")
def enforce_rollback_receipt_boundary(self) -> "LifecycleEventView":
if self.event_type != "rollback":
if self.executor_receipt_id is not None or self.executor_evidence_refs:
raise ValueError("non-rollback lifecycle event cannot carry a receipt")
return self
if self.approval_event_id is None or self.artifact_record_id is None:
raise ValueError("rollback requires approval and pinned artifact")
if self.event_status == "executed":
if not (self.executor_receipt_id or "").strip():
raise ValueError("executed rollback requires executor receipt id")
if not self.executor_evidence_refs:
raise ValueError("executed rollback requires executor evidence")
if not set(self.executor_evidence_refs).issubset(self.evidence_refs):
raise ValueError("executor evidence must be included in lifecycle evidence")
elif self.executor_receipt_id is not None or self.executor_evidence_refs:
raise ValueError("non-executed rollback cannot carry executor receipt evidence")
return self
class OperationalIncidentView(BaseModel):
incident_record_id: UUID
incident_id: str
error_fingerprint: str
affected_contract: str
evidence_refs: list[str]
pii_included: Literal[False]
created_at: datetime
class RegressionDagNodeView(BaseModel):
node_record_id: UUID
incident_record_id: UUID
node_id: str
node_type: Literal["reproduction_test", "implementation", "e2e", "runtime_proof"]
depends_on_record_ids: list[UUID]
evidence_ref: str | None = None
node_status: Literal["pending", "passed", "failed"]
created_at: datetime
class ContinuousImprovementViewResponse(BaseModel):
model_config = ConfigDict(protected_namespaces=())
content_qualifications: list[ContentQualificationView]
model_change_gates: list[ModelChangeGateView]
release_gates: list[ReleaseGateView]
gate_artifacts: list[GateArtifactView]
approvals: list[HumanApprovalView]
catalog_entries: list[CatalogEntryView]
lifecycle_events: list[LifecycleEventView]
incidents: list[OperationalIncidentView]
regression_dag_nodes: list[RegressionDagNodeView]
data_classification: SyntheticDataClassification
silent_auto_promotion_allowed: Literal[False]
raw_transcript_included: Literal[False]
pii_included: Literal[False]
clinical_claim_allowed: Literal[False]
def _artifact_dict(value: GateArtifact) -> dict[str, object]:
return value.model_dump(mode="python")
def _raise_store_error(exc: Exception) -> None:
if isinstance(exc, continuous_improvement_store.ContinuousImprovementConflictError):
raise HTTPException(status_code=409, detail=str(exc)) from exc
if isinstance(exc, continuous_improvement_store.ContinuousImprovementNotFoundError):
raise HTTPException(status_code=404, detail=str(exc)) from exc
raise HTTPException(status_code=422, detail=str(exc)) from exc
@router.post(
"/internal/continuous-improvement/agentic-content-pipelines",
response_model=AgenticContentPipelineResponse,
status_code=201,
)
async def create_agentic_content_pipeline(
request: AgenticContentPipelineRequest, conn: ResearchDB
) -> AgenticContentPipelineResponse:
"""Run real model-owned generation/review/judging before pending approval."""
try:
result = await continuous_improvement_agentic.run_agentic_content_pipeline(
conn=conn,
engine=engine_client,
submission_id=request.submission_id,
pipeline_id=request.pipeline_id,
benchmark_record_id=request.benchmark_record_id,
qualification_id=request.qualification_id,
source_packs=request.source_packs,
content_kind=request.content_kind,
difficulty_level=request.difficulty_level,
variant_count=request.variant_count,
prompt_version=request.prompt_version,
trigger_kind="source_pack",
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
except continuous_improvement_agentic.AgenticPipelineRejectedError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
except continuous_improvement_agentic.AgenticPipelineExecutionError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return AgenticContentPipelineResponse.model_validate(result.model_dump(mode="json"))
@router.post(
"/internal/continuous-improvement/incidents/{incident_record_id}/adversarial-content-pipelines",
response_model=AgenticContentPipelineResponse,
status_code=201,
)
async def create_incident_adversarial_content_pipeline(
incident_record_id: UUID,
request: IncidentAdversarialPipelineRequest,
conn: ResearchDB,
) -> AgenticContentPipelineResponse:
"""Turn a persisted metadata-only operational failure into a gated benchmark."""
try:
incident = await continuous_improvement_store.read_operational_incident(
conn, incident_record_id=incident_record_id
)
source_pack = (
continuous_improvement_agentic.source_pack_from_operational_incident(
incident
)
)
result = await continuous_improvement_agentic.run_agentic_content_pipeline(
conn=conn,
engine=engine_client,
submission_id=request.submission_id,
pipeline_id=request.pipeline_id,
benchmark_record_id=request.benchmark_record_id,
qualification_id=request.qualification_id,
source_packs=[source_pack],
content_kind="benchmark",
difficulty_level=request.difficulty_level,
variant_count=request.variant_count,
prompt_version=request.prompt_version,
trigger_kind="operational_incident",
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
except continuous_improvement_agentic.AgenticPipelineRejectedError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
except continuous_improvement_agentic.AgenticPipelineExecutionError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return AgenticContentPipelineResponse.model_validate(result.model_dump(mode="json"))
@router.post(
"/internal/continuous-improvement/content-pipelines",
response_model=ContentPipelineResponse,
status_code=201,
)
async def create_content_pipeline(
request: ContentPipelineRequest, conn: ResearchDB
) -> ContentPipelineResponse:
try:
result = await continuous_improvement_store.submit_content_pipeline(
conn,
submission_id=request.submission_id,
pipeline_id=request.pipeline_id,
benchmark_record_id=request.benchmark_record_id,
qualification_id=request.qualification_id,
draft=request.draft,
sources=request.sources,
reviews=request.reviews,
benchmark=request.benchmark,
)
except (ValueError, continuous_improvement_store.ContinuousImprovementError) as exc:
_raise_store_error(exc)
return ContentPipelineResponse.model_validate(result)
@router.post(
"/internal/continuous-improvement/model-change-gates",
response_model=ModelChangeGateResponse,
status_code=201,
)
async def create_model_change_gate(
request: ModelChangeGateRequest, conn: ResearchDB
) -> ModelChangeGateResponse:
try:
result = await continuous_improvement_store.submit_model_change_gate(
conn,
submission_id=request.submission_id,
gate_id=request.gate_id,
baseline_snapshot_record_id=request.baseline_snapshot_record_id,
candidate_snapshot_record_id=request.candidate_snapshot_record_id,
baseline=request.baseline,
candidate=request.candidate,
baseline_artifact=_artifact_dict(request.artifacts.baseline),
threshold_artifact=_artifact_dict(request.artifacts.threshold),
provenance_artifacts=[
_artifact_dict(item) for item in request.artifacts.provenance
],
rollback_artifact=_artifact_dict(request.artifacts.rollback),
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
return ModelChangeGateResponse.model_validate(result)
@router.post(
"/internal/continuous-improvement/release-gates",
response_model=ReleaseGateResponse,
status_code=201,
)
async def create_release_gate(
request: ReleaseGateRequest, conn: ResearchDB
) -> ReleaseGateResponse:
try:
result = await continuous_improvement_store.submit_release_gate(
conn,
submission_id=request.submission_id,
gate_id=request.gate_id,
manifest=request.manifest,
baseline_artifact=_artifact_dict(request.artifacts.baseline),
threshold_artifact=_artifact_dict(request.artifacts.threshold),
provenance_artifacts=[
_artifact_dict(item) for item in request.artifacts.provenance
],
rollback_artifact=_artifact_dict(request.artifacts.rollback),
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
return ReleaseGateResponse.model_validate(result)
@router.post(
"/internal/continuous-improvement/incidents",
response_model=IncidentDagResponse,
status_code=201,
)
async def create_incident_dag(
request: IncidentDagRequest, conn: ResearchDB
) -> IncidentDagResponse:
try:
result = await continuous_improvement_store.submit_incident_dag(
conn,
submission_id=request.submission_id,
incident_record_id=request.incident_record_id,
incident=request.incident,
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
return IncidentDagResponse.model_validate(result)
@router.post(
"/continuous-improvement/approvals",
response_model=HumanApprovalResponse,
status_code=201,
)
async def create_human_approval(
request: HumanApprovalRequest,
principal: AdminPrincipal,
conn: AdminDB,
settings: Annotated[Settings, Depends(get_settings)],
) -> HumanApprovalResponse:
try:
rollback_executor = (
continuous_improvement_agentic.build_configured_rollback_executor(settings)
if request.decision == "authorize_rollback"
else None
)
result = await continuous_improvement_store.append_human_approval(
conn,
submission_id=request.submission_id,
approval_event_id=request.approval_event_id,
effect_record_id=request.effect_record_id,
target_kind=request.target_kind,
target_id=request.target_id,
decision=request.decision,
actor_uid=UUID(principal.user_id),
reason_code=request.reason_code,
evidence_refs=request.evidence_refs,
rollback_executor=rollback_executor,
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
return HumanApprovalResponse.model_validate(result)
@router.post(
"/internal/continuous-improvement/monitor-events",
response_model=MonitorEventResponse,
status_code=201,
)
async def create_monitor_event(
request: MonitorEventRequest, conn: ResearchDB
) -> MonitorEventResponse:
try:
result = await continuous_improvement_store.append_monitor_event(
conn,
submission_id=request.submission_id,
lifecycle_event_id=request.lifecycle_event_id,
target_kind=request.target_kind,
target_id=request.target_id,
event_status=request.event_status,
evidence_refs=request.evidence_refs,
)
except continuous_improvement_store.ContinuousImprovementError as exc:
_raise_store_error(exc)
return MonitorEventResponse.model_validate(result)
@router.get(
"/internal/continuous-improvement",
response_model=ContinuousImprovementViewResponse,
)
async def read_internal_continuous_improvement(
conn: ResearchDB,
) -> ContinuousImprovementViewResponse:
result = await continuous_improvement_store.read_continuous_improvement_view(conn)
return ContinuousImprovementViewResponse.model_validate(result)
@router.get(
"/continuous-improvement",
response_model=ContinuousImprovementViewResponse,
)
async def read_admin_continuous_improvement(
_principal: AdminPrincipal,
conn: AdminDB,
) -> ContinuousImprovementViewResponse:
result = await continuous_improvement_store.read_continuous_improvement_view(conn)
return ContinuousImprovementViewResponse.model_validate(result)
@router.get(
"/continuous-improvement/catalog",
response_model=ApprovedCatalogConsumerResponse,
)
async def read_admin_approved_catalog(
_principal: AdminPrincipal,
conn: AdminDB,
) -> ApprovedCatalogConsumerResponse:
entries = await continuous_improvement_store.read_approved_catalog_entries(conn)
return ApprovedCatalogConsumerResponse(
entries=[ApprovedCatalogConsumerEntry.model_validate(item) for item in entries],
data_classification="synthetic_replay_red_team_coverage_drift",
)
__all__ = [
"INTERNAL_TOKEN_HEADER",
"continuous_improvement_internal_db",
"continuous_improvement_admin_db",
"router",
]