Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
from dataclasses import asdict
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from api.database import get_session
|
|
from api.config import settings
|
|
from api.schemas.identifier_migration import (
|
|
IdentifierMigrationApplySubmit,
|
|
SealedProjectionRepairReceiptRead,
|
|
SealedProjectionRepairSubmit,
|
|
)
|
|
from api.services.work_record_identifier_migration import (
|
|
IdentifierMigrationError,
|
|
apply_repository_identifier_migration,
|
|
repair_absent_prederivation_projection,
|
|
reverse_repository_identifier_migration,
|
|
)
|
|
|
|
router = APIRouter(prefix="/identifier-migrations", tags=["identifier-migrations"])
|
|
|
|
|
|
@router.post("/repositories/{repo_slug}/apply")
|
|
async def apply_identifier_migration(
|
|
repo_slug: str,
|
|
body: IdentifierMigrationApplySubmit,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict:
|
|
"""Apply one sealed repository-atomic identifier convergence transaction."""
|
|
if settings.state_hub_instance_role != "primary":
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail={
|
|
"message": "identifier migration writes require the primary State Hub",
|
|
"instance_role": settings.state_hub_instance_role,
|
|
"instance_label": settings.state_hub_instance_label,
|
|
},
|
|
)
|
|
if not body.primary_confirmed:
|
|
raise HTTPException(status_code=409, detail="explicit primary confirmation is required")
|
|
if body.plan.get("plan_sha256") != body.expected_plan_sha256:
|
|
raise HTTPException(status_code=409, detail="explicit plan SHA-256 does not match sealed plan")
|
|
try:
|
|
result = await apply_repository_identifier_migration(session, body.plan, repo_slug)
|
|
except IdentifierMigrationError as exc:
|
|
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
from api.routers.workstreams import _invalidate_workplan_index_cache
|
|
|
|
_invalidate_workplan_index_cache()
|
|
return {
|
|
"schema": "state-hub.identifier-migration-apply.v1",
|
|
"instance_role": settings.state_hub_instance_role,
|
|
"instance_label": settings.state_hub_instance_label,
|
|
"result": asdict(result),
|
|
}
|
|
|
|
|
|
@router.post("/repositories/{repo_slug}/reverse")
|
|
async def reverse_identifier_migration(
|
|
repo_slug: str,
|
|
body: IdentifierMigrationApplySubmit,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict:
|
|
"""Reverse one sealed repository migration after a failed file phase."""
|
|
if settings.state_hub_instance_role != "primary":
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail={
|
|
"message": "identifier migration writes require the primary State Hub",
|
|
"instance_role": settings.state_hub_instance_role,
|
|
"instance_label": settings.state_hub_instance_label,
|
|
},
|
|
)
|
|
if not body.primary_confirmed:
|
|
raise HTTPException(status_code=409, detail="explicit primary confirmation is required")
|
|
if body.plan.get("plan_sha256") != body.expected_plan_sha256:
|
|
raise HTTPException(status_code=409, detail="explicit plan SHA-256 does not match sealed plan")
|
|
try:
|
|
result = await reverse_repository_identifier_migration(session, body.plan, repo_slug)
|
|
except IdentifierMigrationError as exc:
|
|
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
from api.routers.workstreams import _invalidate_workplan_index_cache
|
|
|
|
_invalidate_workplan_index_cache()
|
|
return {
|
|
"schema": "state-hub.identifier-migration-reverse.v1",
|
|
"instance_role": settings.state_hub_instance_role,
|
|
"instance_label": settings.state_hub_instance_label,
|
|
"result": asdict(result),
|
|
}
|
|
|
|
|
|
@router.post(
|
|
"/sealed-projection-repairs",
|
|
response_model=SealedProjectionRepairReceiptRead,
|
|
)
|
|
async def repair_sealed_projection(
|
|
body: SealedProjectionRepairSubmit,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict:
|
|
"""Restore one exact pre-derivation workplan unit into an absent projection."""
|
|
try:
|
|
receipt = await repair_absent_prederivation_projection(
|
|
session,
|
|
body.plan,
|
|
body.repo_slug,
|
|
body.unit,
|
|
expected_plan_sha256=body.expected_plan_sha256,
|
|
source_revision=body.source_revision,
|
|
source_fingerprint=body.source_fingerprint,
|
|
source_clean=body.source_clean,
|
|
source_synchronized=body.source_synchronized,
|
|
primary_confirmed=body.primary_confirmed,
|
|
projection_identity=body.projection_identity,
|
|
)
|
|
except IdentifierMigrationError as exc:
|
|
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
return asdict(receipt)
|