skills + agents
This commit is contained in:
@@ -1477,6 +1477,44 @@ def test_mutation_endpoints_surface_session_version_conflict_payload(
|
||||
# [/DEF:test_mutation_endpoints_surface_session_version_conflict_payload:Function]
|
||||
|
||||
|
||||
# [DEF:test_update_session_surfaces_commit_time_session_version_conflict_payload:Function]
|
||||
# @RELATION: BINDS_TO -> DatasetReviewApiTests
|
||||
# @PURPOSE: Session lifecycle mutation should return deterministic 409 conflict semantics when commit-time optimistic locking rejects a stale write.
|
||||
def test_update_session_surfaces_commit_time_session_version_conflict_payload(
|
||||
dataset_review_api_dependencies,
|
||||
):
|
||||
session = _make_session()
|
||||
repository = MagicMock()
|
||||
repository.load_session_detail.return_value = session
|
||||
repository.require_session_version.return_value = session
|
||||
repository.commit_session_mutation.side_effect = (
|
||||
DatasetReviewSessionVersionConflictError(
|
||||
session_id="sess-1",
|
||||
expected_version=0,
|
||||
actual_version=1,
|
||||
)
|
||||
)
|
||||
repository.db = MagicMock()
|
||||
repository.event_logger = MagicMock(spec=SessionEventLogger)
|
||||
|
||||
app.dependency_overrides[_get_repository] = lambda: repository
|
||||
|
||||
response = client.patch(
|
||||
"/api/dataset-orchestration/sessions/sess-1",
|
||||
json={"status": "paused"},
|
||||
headers={"X-Session-Version": "0"},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
payload = response.json()["detail"]
|
||||
assert payload["error_code"] == "session_version_conflict"
|
||||
assert payload["expected_version"] == 0
|
||||
assert payload["actual_version"] == 1
|
||||
|
||||
|
||||
# [/DEF:test_update_session_surfaces_commit_time_session_version_conflict_payload:Function]
|
||||
|
||||
|
||||
# [DEF:test_execution_snapshot_includes_recovered_imported_filters_without_template_mapping:Function]
|
||||
# @RELATION: BINDS_TO -> DatasetReviewApiTests
|
||||
# @PURPOSE: Recovered imported filters with values should flow into preview filter context even when no template variable mapping exists.
|
||||
|
||||
@@ -69,8 +69,8 @@ class RevokeRequest(dict):
|
||||
# @PRE: Payload contains required fields (id, version, source_snapshot_ref, created_by).
|
||||
# @POST: Candidate is saved in repository.
|
||||
# @RETURN: CandidateDTO
|
||||
# @RELATION: CALLS -> [CleanReleaseRepository.save_candidate]
|
||||
# @RELATION: DEPENDS_ON -> [CandidateDTO]
|
||||
# @RELATION: DEPENDS_ON -> [CleanReleaseRepository]
|
||||
# @RELATION: DEPENDS_ON -> [clean_release_dto]
|
||||
@router.post(
|
||||
"/candidates", response_model=CandidateDTO, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
@@ -105,7 +105,7 @@ async def register_candidate(
|
||||
# @PURPOSE: Associate artifacts with a release candidate.
|
||||
# @PRE: Candidate exists.
|
||||
# @POST: Artifacts are processed (placeholder).
|
||||
# @RELATION: CALLS -> [CleanReleaseRepository.get_candidate]
|
||||
# @RELATION: DEPENDS_ON -> [CleanReleaseRepository]
|
||||
@router.post("/candidates/{candidate_id}/artifacts")
|
||||
async def import_artifacts(
|
||||
candidate_id: str,
|
||||
@@ -140,8 +140,7 @@ async def import_artifacts(
|
||||
# @PRE: Candidate exists.
|
||||
# @POST: Manifest is created and saved.
|
||||
# @RETURN: ManifestDTO
|
||||
# @RELATION: CALLS -> [CleanReleaseRepository.save_manifest]
|
||||
# @RELATION: CALLS -> [CleanReleaseRepository.get_candidate]
|
||||
# @RELATION: DEPENDS_ON -> [CleanReleaseRepository]
|
||||
@router.post(
|
||||
"/candidates/{candidate_id}/manifests",
|
||||
response_model=ManifestDTO,
|
||||
|
||||
@@ -480,16 +480,7 @@ def _enforce_session_version(
|
||||
"actual_version": exc.actual_version,
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail={
|
||||
"error_code": "session_version_conflict",
|
||||
"message": str(exc),
|
||||
"session_id": exc.session_id,
|
||||
"expected_version": exc.expected_version,
|
||||
"actual_version": exc.actual_version,
|
||||
},
|
||||
) from exc
|
||||
raise _build_session_version_conflict_http_exception(exc) from exc
|
||||
logger.reflect(
|
||||
"Dataset review optimistic-lock version accepted",
|
||||
extra={
|
||||
@@ -498,9 +489,33 @@ def _enforce_session_version(
|
||||
},
|
||||
)
|
||||
return session
|
||||
|
||||
|
||||
# [/DEF:_enforce_session_version:Function]
|
||||
|
||||
|
||||
# [DEF:_build_session_version_conflict_http_exception:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Normalize optimistic-lock conflict errors into deterministic dataset-review HTTP 409 responses.
|
||||
# @RELATION: [DEPENDS_ON] ->[DatasetReviewSessionVersionConflictError]
|
||||
def _build_session_version_conflict_http_exception(
|
||||
exc: DatasetReviewSessionVersionConflictError,
|
||||
) -> HTTPException:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail={
|
||||
"error_code": "session_version_conflict",
|
||||
"message": str(exc),
|
||||
"session_id": exc.session_id,
|
||||
"expected_version": exc.expected_version,
|
||||
"actual_version": exc.actual_version,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# [/DEF:_build_session_version_conflict_http_exception:Function]
|
||||
|
||||
|
||||
# [DEF:_prepare_owned_session_mutation:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Resolve owner-scoped mutation session and enforce optimistic-lock version before changing dataset review state.
|
||||
@@ -524,7 +539,9 @@ def _prepare_owned_session_mutation(
|
||||
)
|
||||
session = _get_owned_session_or_404(repository, session_id, current_user)
|
||||
_require_owner_mutation_scope(session, current_user)
|
||||
guarded_session = _enforce_session_version(repository, session, expected_version)
|
||||
guarded_session = _enforce_session_version(
|
||||
repository, session, expected_version
|
||||
)
|
||||
logger.reflect(
|
||||
"Dataset review mutation session passed ownership and version guards",
|
||||
extra={
|
||||
@@ -534,6 +551,8 @@ def _prepare_owned_session_mutation(
|
||||
},
|
||||
)
|
||||
return guarded_session
|
||||
|
||||
|
||||
# [/DEF:_prepare_owned_session_mutation:Function]
|
||||
|
||||
|
||||
@@ -556,11 +575,21 @@ def _commit_owned_session_mutation(
|
||||
"Committing dataset review mutation",
|
||||
extra={"session_id": session.session_id},
|
||||
)
|
||||
repository.bump_session_version(session)
|
||||
repository.db.commit()
|
||||
repository.db.refresh(session)
|
||||
for target in refresh_targets or []:
|
||||
repository.db.refresh(target)
|
||||
try:
|
||||
repository.commit_session_mutation(
|
||||
session,
|
||||
refresh_targets=refresh_targets,
|
||||
)
|
||||
except DatasetReviewSessionVersionConflictError as exc:
|
||||
logger.explore(
|
||||
"Dataset review mutation commit detected stale version",
|
||||
extra={
|
||||
"session_id": exc.session_id,
|
||||
"expected_version": exc.expected_version,
|
||||
"actual_version": exc.actual_version,
|
||||
},
|
||||
)
|
||||
raise _build_session_version_conflict_http_exception(exc) from exc
|
||||
logger.reflect(
|
||||
"Dataset review mutation committed and refreshed",
|
||||
extra={
|
||||
@@ -570,6 +599,8 @@ def _commit_owned_session_mutation(
|
||||
},
|
||||
)
|
||||
return session
|
||||
|
||||
|
||||
# [/DEF:_commit_owned_session_mutation:Function]
|
||||
|
||||
|
||||
@@ -678,7 +709,7 @@ def _get_owned_session_or_404(
|
||||
"Resolving dataset review session in current ownership scope",
|
||||
extra={"session_id": session_id, "user_id": current_user.id},
|
||||
)
|
||||
session = repository.load_detail(session_id, current_user.id)
|
||||
session = repository.load_session_detail(session_id, current_user.id)
|
||||
if session is None:
|
||||
logger.explore(
|
||||
"Dataset review session not found in current ownership scope",
|
||||
@@ -1224,9 +1255,21 @@ async def list_sessions(
|
||||
# @POST: returns persisted session summary scoped to the authenticated user.
|
||||
# @SIDE_EFFECT: persists session/profile/findings and may enqueue recovery task.
|
||||
# @DATA_CONTRACT: Input[StartSessionRequest] -> Output[SessionSummary]
|
||||
@router.post('/sessions', response_model=SessionSummary, status_code=status.HTTP_201_CREATED, dependencies=[Depends(_require_auto_review_flag), Depends(has_permission('dataset:session', 'MANAGE'))])
|
||||
async def start_session(request: StartSessionRequest, orchestrator: DatasetReviewOrchestrator=Depends(_get_orchestrator), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('start_session'):
|
||||
@router.post(
|
||||
"/sessions",
|
||||
response_model=SessionSummary,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(has_permission("dataset:session", "MANAGE")),
|
||||
],
|
||||
)
|
||||
async def start_session(
|
||||
request: StartSessionRequest,
|
||||
orchestrator: DatasetReviewOrchestrator = Depends(_get_orchestrator),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("start_session"):
|
||||
logger.reason(
|
||||
"Starting dataset review session",
|
||||
extra={
|
||||
@@ -1236,11 +1279,25 @@ async def start_session(request: StartSessionRequest, orchestrator: DatasetRevie
|
||||
},
|
||||
)
|
||||
try:
|
||||
result = orchestrator.start_session(StartSessionCommand(user=current_user, environment_id=request.environment_id, source_kind=request.source_kind, source_input=request.source_input))
|
||||
result = orchestrator.start_session(
|
||||
StartSessionCommand(
|
||||
user=current_user,
|
||||
environment_id=request.environment_id,
|
||||
source_kind=request.source_kind,
|
||||
source_input=request.source_input,
|
||||
)
|
||||
)
|
||||
except ValueError as exc:
|
||||
logger.explore('Dataset review session start rejected', extra={'user_id': current_user.id, 'error': str(exc)})
|
||||
logger.explore(
|
||||
"Dataset review session start rejected",
|
||||
extra={"user_id": current_user.id, "error": str(exc)},
|
||||
)
|
||||
detail = str(exc)
|
||||
status_code = status.HTTP_404_NOT_FOUND if detail == 'Environment not found' else status.HTTP_400_BAD_REQUEST
|
||||
status_code = (
|
||||
status.HTTP_404_NOT_FOUND
|
||||
if detail == "Environment not found"
|
||||
else status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
raise HTTPException(status_code=status_code, detail=detail) from exc
|
||||
logger.reflect(
|
||||
"Dataset review session started and serialized",
|
||||
@@ -1250,6 +1307,8 @@ async def start_session(request: StartSessionRequest, orchestrator: DatasetRevie
|
||||
},
|
||||
)
|
||||
return _serialize_session_summary(result.session)
|
||||
|
||||
|
||||
# [/DEF:start_session:Function]
|
||||
|
||||
|
||||
@@ -1295,9 +1354,22 @@ async def get_session_detail(
|
||||
# @POST: returns updated summary without changing ownership or unrelated aggregates.
|
||||
# @SIDE_EFFECT: mutates session lifecycle fields in persistence.
|
||||
# @DATA_CONTRACT: Input[UpdateSessionRequest] -> Output[SessionSummary]
|
||||
@router.patch('/sessions/{session_id}', response_model=SessionSummary, dependencies=[Depends(_require_auto_review_flag), Depends(has_permission('dataset:session', 'MANAGE'))])
|
||||
async def update_session(session_id: str, request: UpdateSessionRequest, session_version: int=Depends(_require_session_version_header), repository: DatasetReviewSessionRepository=Depends(_get_repository), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('update_session'):
|
||||
@router.patch(
|
||||
"/sessions/{session_id}",
|
||||
response_model=SessionSummary,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(has_permission("dataset:session", "MANAGE")),
|
||||
],
|
||||
)
|
||||
async def update_session(
|
||||
session_id: str,
|
||||
request: UpdateSessionRequest,
|
||||
session_version: int = Depends(_require_session_version_header),
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("update_session"):
|
||||
logger.reason(
|
||||
"Updating dataset review session lifecycle state",
|
||||
extra={
|
||||
@@ -1306,17 +1378,31 @@ async def update_session(session_id: str, request: UpdateSessionRequest, session
|
||||
"requested_status": request.status.value,
|
||||
},
|
||||
)
|
||||
session = _prepare_owned_session_mutation(repository, session_id, current_user, session_version)
|
||||
session = _prepare_owned_session_mutation(
|
||||
repository, session_id, current_user, session_version
|
||||
)
|
||||
session_record = cast(Any, session)
|
||||
session_record.status = request.status
|
||||
if request.status == SessionStatus.PAUSED:
|
||||
session_record.recommended_action = RecommendedAction.RESUME_SESSION
|
||||
elif request.status in {SessionStatus.ARCHIVED, SessionStatus.CANCELLED, SessionStatus.COMPLETED}:
|
||||
elif request.status in {
|
||||
SessionStatus.ARCHIVED,
|
||||
SessionStatus.CANCELLED,
|
||||
SessionStatus.COMPLETED,
|
||||
}:
|
||||
session_record.active_task_id = None
|
||||
repository.bump_session_version(session)
|
||||
repository.db.commit()
|
||||
repository.db.refresh(session)
|
||||
_record_session_event(repository, session, current_user, event_type='session_status_updated', event_summary='Dataset review session lifecycle updated', event_details={'status': session_record.status.value, 'version': session_record.version})
|
||||
_commit_owned_session_mutation(repository, session)
|
||||
_record_session_event(
|
||||
repository,
|
||||
session,
|
||||
current_user,
|
||||
event_type="session_status_updated",
|
||||
event_summary="Dataset review session lifecycle updated",
|
||||
event_details={
|
||||
"status": session_record.status.value,
|
||||
"version": session_record.version,
|
||||
},
|
||||
)
|
||||
logger.reflect(
|
||||
"Dataset review session lifecycle updated",
|
||||
extra={
|
||||
@@ -1327,11 +1413,11 @@ async def update_session(session_id: str, request: UpdateSessionRequest, session
|
||||
},
|
||||
)
|
||||
return _serialize_session_summary(session)
|
||||
|
||||
|
||||
# [/DEF:update_session:Function]
|
||||
|
||||
|
||||
from src.logger import belief_scope, logger
|
||||
|
||||
# [DEF:delete_session:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Archive or hard-delete a session owned by the current user.
|
||||
@@ -1340,9 +1426,22 @@ from src.logger import belief_scope, logger
|
||||
# @POST: session is archived or deleted and no foreign-session existence is disclosed.
|
||||
# @SIDE_EFFECT: mutates or deletes persisted session aggregate.
|
||||
# @DATA_CONTRACT: Input[session_id:str,hard_delete:bool] -> Output[HTTP 204]
|
||||
@router.delete('/sessions/{session_id}', status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(_require_auto_review_flag), Depends(has_permission('dataset:session', 'MANAGE'))])
|
||||
async def delete_session(session_id: str, hard_delete: bool=Query(False), session_version: int=Depends(_require_session_version_header), repository: DatasetReviewSessionRepository=Depends(_get_repository), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('delete_session'):
|
||||
@router.delete(
|
||||
"/sessions/{session_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(has_permission("dataset:session", "MANAGE")),
|
||||
],
|
||||
)
|
||||
async def delete_session(
|
||||
session_id: str,
|
||||
hard_delete: bool = Query(False),
|
||||
session_version: int = Depends(_require_session_version_header),
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("delete_session"):
|
||||
logger.reason(
|
||||
"Deleting or archiving dataset review session",
|
||||
extra={
|
||||
@@ -1351,9 +1450,18 @@ async def delete_session(session_id: str, hard_delete: bool=Query(False), sessio
|
||||
"hard_delete": hard_delete,
|
||||
},
|
||||
)
|
||||
session = _prepare_owned_session_mutation(repository, session_id, current_user, session_version)
|
||||
session = _prepare_owned_session_mutation(
|
||||
repository, session_id, current_user, session_version
|
||||
)
|
||||
if hard_delete:
|
||||
_record_session_event(repository, session, current_user, event_type='session_deleted', event_summary='Dataset review session hard-deleted', event_details={'hard_delete': True})
|
||||
_record_session_event(
|
||||
repository,
|
||||
session,
|
||||
current_user,
|
||||
event_type="session_deleted",
|
||||
event_summary="Dataset review session hard-deleted",
|
||||
event_details={"hard_delete": True},
|
||||
)
|
||||
repository.db.delete(session)
|
||||
repository.db.commit()
|
||||
logger.reflect(
|
||||
@@ -1365,7 +1473,14 @@ async def delete_session(session_id: str, hard_delete: bool=Query(False), sessio
|
||||
session_record.status = SessionStatus.ARCHIVED
|
||||
session_record.active_task_id = None
|
||||
_commit_owned_session_mutation(repository, session)
|
||||
_record_session_event(repository, session, current_user, event_type='session_archived', event_summary='Dataset review session archived', event_details={'hard_delete': False, 'version': session_record.version})
|
||||
_record_session_event(
|
||||
repository,
|
||||
session,
|
||||
current_user,
|
||||
event_type="session_archived",
|
||||
event_summary="Dataset review session archived",
|
||||
event_details={"hard_delete": False, "version": session_record.version},
|
||||
)
|
||||
logger.reflect(
|
||||
"Dataset review session archive committed",
|
||||
extra={
|
||||
@@ -1375,6 +1490,8 @@ async def delete_session(session_id: str, hard_delete: bool=Query(False), sessio
|
||||
},
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# [/DEF:delete_session:Function]
|
||||
|
||||
|
||||
@@ -1386,11 +1503,26 @@ async def delete_session(session_id: str, hard_delete: bool=Query(False), sessio
|
||||
# @POST: returns ownership-scoped export payload without fabricating unrelated artifacts.
|
||||
# @SIDE_EFFECT: none beyond response construction.
|
||||
# @DATA_CONTRACT: Input[session_id:str,format:ArtifactFormat] -> Output[ExportArtifactResponse]
|
||||
@router.get('/sessions/{session_id}/exports/documentation', response_model=ExportArtifactResponse, dependencies=[Depends(_require_auto_review_flag), Depends(has_permission('dataset:session', 'READ'))])
|
||||
async def export_documentation(session_id: str, format: ArtifactFormat=Query(ArtifactFormat.JSON), repository: DatasetReviewSessionRepository=Depends(_get_repository), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('export_documentation'):
|
||||
@router.get(
|
||||
"/sessions/{session_id}/exports/documentation",
|
||||
response_model=ExportArtifactResponse,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(has_permission("dataset:session", "READ")),
|
||||
],
|
||||
)
|
||||
async def export_documentation(
|
||||
session_id: str,
|
||||
format: ArtifactFormat = Query(ArtifactFormat.JSON),
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("export_documentation"):
|
||||
if format not in {ArtifactFormat.JSON, ArtifactFormat.MARKDOWN}:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Only json and markdown exports are supported')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Only json and markdown exports are supported",
|
||||
)
|
||||
logger.reason(
|
||||
"Building dataset review documentation export",
|
||||
extra={
|
||||
@@ -1409,7 +1541,17 @@ async def export_documentation(session_id: str, format: ArtifactFormat=Query(Art
|
||||
"format": format.value,
|
||||
},
|
||||
)
|
||||
return ExportArtifactResponse(artifact_id=f'documentation-{session.session_id}-{format.value}', session_id=session.session_id, artifact_type='documentation', format=format.value, storage_ref=export_payload['storage_ref'], created_by_user_id=current_user.id, content=export_payload['content'])
|
||||
return ExportArtifactResponse(
|
||||
artifact_id=f"documentation-{session.session_id}-{format.value}",
|
||||
session_id=session.session_id,
|
||||
artifact_type="documentation",
|
||||
format=format.value,
|
||||
storage_ref=export_payload["storage_ref"],
|
||||
created_by_user_id=current_user.id,
|
||||
content=export_payload["content"],
|
||||
)
|
||||
|
||||
|
||||
# [/DEF:export_documentation:Function]
|
||||
|
||||
|
||||
@@ -1421,11 +1563,26 @@ async def export_documentation(session_id: str, format: ArtifactFormat=Query(Art
|
||||
# @POST: returns explicit validation export payload scoped to current user session access.
|
||||
# @SIDE_EFFECT: none beyond response construction.
|
||||
# @DATA_CONTRACT: Input[session_id:str,format:ArtifactFormat] -> Output[ExportArtifactResponse]
|
||||
@router.get('/sessions/{session_id}/exports/validation', response_model=ExportArtifactResponse, dependencies=[Depends(_require_auto_review_flag), Depends(has_permission('dataset:session', 'READ'))])
|
||||
async def export_validation(session_id: str, format: ArtifactFormat=Query(ArtifactFormat.JSON), repository: DatasetReviewSessionRepository=Depends(_get_repository), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('export_validation'):
|
||||
@router.get(
|
||||
"/sessions/{session_id}/exports/validation",
|
||||
response_model=ExportArtifactResponse,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(has_permission("dataset:session", "READ")),
|
||||
],
|
||||
)
|
||||
async def export_validation(
|
||||
session_id: str,
|
||||
format: ArtifactFormat = Query(ArtifactFormat.JSON),
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("export_validation"):
|
||||
if format not in {ArtifactFormat.JSON, ArtifactFormat.MARKDOWN}:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Only json and markdown exports are supported')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Only json and markdown exports are supported",
|
||||
)
|
||||
logger.reason(
|
||||
"Building dataset review validation export",
|
||||
extra={
|
||||
@@ -1444,7 +1601,17 @@ async def export_validation(session_id: str, format: ArtifactFormat=Query(Artifa
|
||||
"format": format.value,
|
||||
},
|
||||
)
|
||||
return ExportArtifactResponse(artifact_id=f'validation-{session.session_id}-{format.value}', session_id=session.session_id, artifact_type='validation_report', format=format.value, storage_ref=export_payload['storage_ref'], created_by_user_id=current_user.id, content=export_payload['content'])
|
||||
return ExportArtifactResponse(
|
||||
artifact_id=f"validation-{session.session_id}-{format.value}",
|
||||
session_id=session.session_id,
|
||||
artifact_type="validation_report",
|
||||
format=format.value,
|
||||
storage_ref=export_payload["storage_ref"],
|
||||
created_by_user_id=current_user.id,
|
||||
content=export_payload["content"],
|
||||
)
|
||||
|
||||
|
||||
# [/DEF:export_validation:Function]
|
||||
|
||||
|
||||
@@ -1456,18 +1623,46 @@ async def export_validation(session_id: str, format: ArtifactFormat=Query(Artifa
|
||||
# @POST: Returns at most one active clarification question with why_it_matters, current_guess, and ordered options; sessions without a clarification record return a non-blocking empty state.
|
||||
# @SIDE_EFFECT: May normalize clarification pointer and readiness state in persistence.
|
||||
# @DATA_CONTRACT: Input[session_id:str] -> Output[ClarificationStateResponse]
|
||||
@router.get('/sessions/{session_id}/clarification', response_model=ClarificationStateResponse, dependencies=[Depends(_require_auto_review_flag), Depends(_require_clarification_flag), Depends(has_permission('dataset:session', 'READ'))])
|
||||
async def get_clarification_state(session_id: str, repository: DatasetReviewSessionRepository=Depends(_get_repository), clarification_engine: ClarificationEngine=Depends(_get_clarification_engine), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('get_clarification_state'):
|
||||
logger.reason('Belief protocol reasoning checkpoint for get_clarification_state')
|
||||
@router.get(
|
||||
"/sessions/{session_id}/clarification",
|
||||
response_model=ClarificationStateResponse,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(_require_clarification_flag),
|
||||
Depends(has_permission("dataset:session", "READ")),
|
||||
],
|
||||
)
|
||||
async def get_clarification_state(
|
||||
session_id: str,
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
clarification_engine: ClarificationEngine = Depends(_get_clarification_engine),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("get_clarification_state"):
|
||||
logger.reason(
|
||||
"Belief protocol reasoning checkpoint for get_clarification_state"
|
||||
)
|
||||
session = _get_owned_session_or_404(repository, session_id, current_user)
|
||||
if not session.clarification_sessions:
|
||||
logger.reflect('Belief protocol postcondition checkpoint for get_clarification_state')
|
||||
logger.reflect(
|
||||
"Belief protocol postcondition checkpoint for get_clarification_state"
|
||||
)
|
||||
return _serialize_empty_clarification_state()
|
||||
clarification_session = _get_latest_clarification_session_or_404(session)
|
||||
current_question = clarification_engine.build_question_payload(session)
|
||||
logger.reflect('Belief protocol postcondition checkpoint for get_clarification_state')
|
||||
return _serialize_clarification_state(ClarificationStateResult(clarification_session=clarification_session, current_question=current_question, session=session, changed_findings=[]))
|
||||
logger.reflect(
|
||||
"Belief protocol postcondition checkpoint for get_clarification_state"
|
||||
)
|
||||
return _serialize_clarification_state(
|
||||
ClarificationStateResult(
|
||||
clarification_session=clarification_session,
|
||||
current_question=current_question,
|
||||
session=session,
|
||||
changed_findings=[],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# [/DEF:get_clarification_state:Function]
|
||||
|
||||
|
||||
@@ -1479,15 +1674,42 @@ async def get_clarification_state(session_id: str, repository: DatasetReviewSess
|
||||
# @POST: Clarification session enters active state with one current question or completes deterministically when no unresolved items remain.
|
||||
# @SIDE_EFFECT: Mutates clarification pointer, readiness, and recommended action.
|
||||
# @DATA_CONTRACT: Input[session_id:str] -> Output[ClarificationStateResponse]
|
||||
@router.post('/sessions/{session_id}/clarification/resume', response_model=ClarificationStateResponse, dependencies=[Depends(_require_auto_review_flag), Depends(_require_clarification_flag), Depends(has_permission('dataset:session', 'MANAGE'))])
|
||||
async def resume_clarification(session_id: str, session_version: int=Depends(_require_session_version_header), repository: DatasetReviewSessionRepository=Depends(_get_repository), clarification_engine: ClarificationEngine=Depends(_get_clarification_engine), current_user: User=Depends(get_current_user)):
|
||||
with belief_scope('resume_clarification'):
|
||||
logger.reason('Belief protocol reasoning checkpoint for resume_clarification')
|
||||
session = _prepare_owned_session_mutation(repository, session_id, current_user, session_version)
|
||||
@router.post(
|
||||
"/sessions/{session_id}/clarification/resume",
|
||||
response_model=ClarificationStateResponse,
|
||||
dependencies=[
|
||||
Depends(_require_auto_review_flag),
|
||||
Depends(_require_clarification_flag),
|
||||
Depends(has_permission("dataset:session", "MANAGE")),
|
||||
],
|
||||
)
|
||||
async def resume_clarification(
|
||||
session_id: str,
|
||||
session_version: int = Depends(_require_session_version_header),
|
||||
repository: DatasetReviewSessionRepository = Depends(_get_repository),
|
||||
clarification_engine: ClarificationEngine = Depends(_get_clarification_engine),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
with belief_scope("resume_clarification"):
|
||||
logger.reason("Belief protocol reasoning checkpoint for resume_clarification")
|
||||
session = _prepare_owned_session_mutation(
|
||||
repository, session_id, current_user, session_version
|
||||
)
|
||||
clarification_session = _get_latest_clarification_session_or_404(session)
|
||||
current_question = clarification_engine.build_question_payload(session)
|
||||
logger.reflect('Belief protocol postcondition checkpoint for resume_clarification')
|
||||
return _serialize_clarification_state(ClarificationStateResult(clarification_session=clarification_session, current_question=current_question, session=session, changed_findings=[]))
|
||||
logger.reflect(
|
||||
"Belief protocol postcondition checkpoint for resume_clarification"
|
||||
)
|
||||
return _serialize_clarification_state(
|
||||
ClarificationStateResult(
|
||||
clarification_session=clarification_session,
|
||||
current_question=current_question,
|
||||
session=session,
|
||||
changed_findings=[],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# [/DEF:resume_clarification:Function]
|
||||
|
||||
|
||||
@@ -2040,6 +2262,8 @@ async def trigger_preview_generation(
|
||||
expected_version=session_version,
|
||||
)
|
||||
)
|
||||
except DatasetReviewSessionVersionConflictError as exc:
|
||||
raise _build_session_version_conflict_http_exception(exc) from exc
|
||||
except ValueError as exc:
|
||||
detail = str(exc)
|
||||
status_code = (
|
||||
@@ -2108,6 +2332,8 @@ async def launch_dataset(
|
||||
expected_version=session_version,
|
||||
)
|
||||
)
|
||||
except DatasetReviewSessionVersionConflictError as exc:
|
||||
raise _build_session_version_conflict_http_exception(exc) from exc
|
||||
except ValueError as exc:
|
||||
detail = str(exc)
|
||||
status_code = (
|
||||
|
||||
Reference in New Issue
Block a user