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 산출물은 커밋에서 제외했다.
175 lines
6.2 KiB
Python
175 lines
6.2 KiB
Python
"""G8 콘텐츠 승격, 모델 교체 gate, 운영 오류 회귀 DAG 코어."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Iterable
|
|
|
|
from ..contracts.continuous_improvement import (
|
|
AgenticReleaseManifest,
|
|
ApprovedCatalogEntry,
|
|
ContentBenchmarkQualification,
|
|
ContentSourceArtifact,
|
|
GeneratedContentDraft,
|
|
IncidentRegressionDag,
|
|
IndependentRedTeamReview,
|
|
ModelCalibrationSnapshot,
|
|
ModelChangeGateResult,
|
|
OperationalIncident,
|
|
RegressionBacklogNode,
|
|
)
|
|
|
|
|
|
REQUIRED_REVIEW_DIMENSIONS = {
|
|
"safety",
|
|
"identity",
|
|
"answer_leakage",
|
|
"cultural_bias",
|
|
"difficulty",
|
|
"pii",
|
|
"grounding",
|
|
}
|
|
|
|
|
|
def promote_content_to_catalog(
|
|
*,
|
|
draft: GeneratedContentDraft,
|
|
sources: Iterable[ContentSourceArtifact],
|
|
reviews: Iterable[IndependentRedTeamReview],
|
|
benchmark: ContentBenchmarkQualification,
|
|
) -> ApprovedCatalogEntry:
|
|
source_by_id = {item.source_id: item for item in sources}
|
|
if set(draft.source_refs) - set(source_by_id):
|
|
raise ValueError("content draft references an unknown source")
|
|
if any(source_by_id[item].usage_status != "approved" for item in draft.source_refs):
|
|
raise ValueError("content promotion requires approved source usage")
|
|
if draft.visible_answer_overlap_tokens:
|
|
raise ValueError("content promotion blocked by visible answer leakage")
|
|
if draft.pii_findings:
|
|
raise ValueError("content promotion blocked by PII findings")
|
|
if draft.unsupported_clinical_claims:
|
|
raise ValueError("content promotion blocked by unsupported clinical claims")
|
|
|
|
review_items = tuple(reviews)
|
|
if len(review_items) < 2:
|
|
raise ValueError("content promotion requires two independent red-team reviews")
|
|
if len({item.reviewer_agent_id for item in review_items}) != len(review_items):
|
|
raise ValueError("red-team reviewers must be independent")
|
|
if any(item.draft_id != draft.draft_id for item in review_items):
|
|
raise ValueError("red-team review references another draft")
|
|
if any(
|
|
item.reviewed_payload_sha256 != draft.payload_sha256 for item in review_items
|
|
):
|
|
raise ValueError("red-team review payload hash differs from draft")
|
|
covered = {dimension for item in review_items for dimension in item.dimensions}
|
|
if not REQUIRED_REVIEW_DIMENSIONS.issubset(covered):
|
|
raise ValueError("red-team reviews do not cover every required dimension")
|
|
unresolved = [
|
|
finding
|
|
for item in review_items
|
|
for finding in item.findings
|
|
if finding.state == "open"
|
|
or (
|
|
finding.state == "accepted_risk" and finding.severity in {"blocker", "high"}
|
|
)
|
|
]
|
|
if unresolved:
|
|
raise ValueError("content promotion blocked by unresolved red-team findings")
|
|
if benchmark.draft_id != draft.draft_id or not benchmark.qualified:
|
|
raise ValueError("content promotion requires a qualified benchmark")
|
|
|
|
return ApprovedCatalogEntry(
|
|
catalog_entry_id=draft.draft_id.replace("oas-g8-draft-", "oas-g8-catalog-"),
|
|
draft_id=draft.draft_id,
|
|
payload_sha256=draft.payload_sha256,
|
|
source_refs=draft.source_refs,
|
|
review_ids=tuple(item.review_id for item in review_items),
|
|
benchmark_id=benchmark.benchmark_id,
|
|
)
|
|
|
|
|
|
def decide_model_change(
|
|
*,
|
|
baseline: ModelCalibrationSnapshot,
|
|
candidate: ModelCalibrationSnapshot,
|
|
) -> ModelChangeGateResult:
|
|
reasons: list[str] = []
|
|
if candidate.critical_miss_count > baseline.critical_miss_count:
|
|
reasons.append("critical_miss_regression")
|
|
if candidate.leakage_count or candidate.pii_count:
|
|
reasons.append("candidate_privacy_or_leakage_failure")
|
|
if candidate.task_accuracy < baseline.task_accuracy - 0.02:
|
|
reasons.append("task_accuracy_regression")
|
|
if candidate.calibration_error > baseline.calibration_error + 0.02:
|
|
reasons.append("calibration_error_regression")
|
|
if candidate.subgroup_max_gap > baseline.subgroup_max_gap + 0.05:
|
|
reasons.append("subgroup_gap_regression")
|
|
|
|
if candidate.leakage_count or candidate.pii_count or candidate.critical_miss_count:
|
|
decision = "rollback"
|
|
elif reasons:
|
|
decision = "quarantine"
|
|
else:
|
|
decision = "promote"
|
|
reasons.append("candidate_passed_all_calibration_gates")
|
|
return ModelChangeGateResult(
|
|
baseline_snapshot_id=baseline.snapshot_id,
|
|
candidate_snapshot_id=candidate.snapshot_id,
|
|
decision=decision,
|
|
reasons=tuple(reasons),
|
|
rollback_target_snapshot_id=(
|
|
baseline.snapshot_id if decision == "rollback" else None
|
|
),
|
|
)
|
|
|
|
|
|
def build_incident_regression_dag(
|
|
incident: OperationalIncident,
|
|
) -> IncidentRegressionDag:
|
|
prefix = incident.incident_id.replace("oas-g8-incident-", "")
|
|
reproduction_id = f"oas-g8-node-{prefix}-reproduction"
|
|
implementation_id = f"oas-g8-node-{prefix}-implementation"
|
|
e2e_id = f"oas-g8-node-{prefix}-e2e"
|
|
runtime_id = f"oas-g8-node-{prefix}-runtime"
|
|
return IncidentRegressionDag(
|
|
incident_id=incident.incident_id,
|
|
nodes=(
|
|
RegressionBacklogNode(
|
|
node_id=reproduction_id,
|
|
node_type="reproduction_test",
|
|
depends_on=(),
|
|
evidence_ref=incident.evidence_refs[0],
|
|
status="pending",
|
|
),
|
|
RegressionBacklogNode(
|
|
node_id=implementation_id,
|
|
node_type="implementation",
|
|
depends_on=(reproduction_id,),
|
|
status="pending",
|
|
),
|
|
RegressionBacklogNode(
|
|
node_id=e2e_id,
|
|
node_type="e2e",
|
|
depends_on=(implementation_id,),
|
|
status="pending",
|
|
),
|
|
RegressionBacklogNode(
|
|
node_id=runtime_id,
|
|
node_type="runtime_proof",
|
|
depends_on=(e2e_id,),
|
|
status="pending",
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def release_allowed(manifest: AgenticReleaseManifest) -> bool:
|
|
return manifest.releasable
|
|
|
|
|
|
__all__ = [
|
|
"REQUIRED_REVIEW_DIMENSIONS",
|
|
"build_incident_regression_dag",
|
|
"decide_model_change",
|
|
"promote_content_to_catalog",
|
|
"release_allowed",
|
|
]
|