fix: CR endpoint — greenlet bug + UUID format + missing column
Three bugs in the complete handler:
1. SQLAlchemy greenlet: ORM model attribute access triggers sync
lazy-loads in async context. Fix: raw SQL via text() for all
DB operations in the CR handler.
2. UUID format: SQLite stores UUIDs without hyphens (via SQLModel).
Raw SQL comparisons must strip hyphens: str(uuid).replace("-","")
3. Missing received_at: NOT NULL constraint on completion_receipts.
Raw INSERT was missing the column. Added received_at=now().
Full AC/CR cycle now verified:
AC → 200, principal DID resolved from Keycloak token
CR → 200, receipt ID + Chronicle CID returned
Session → 200, chain of custody queryable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4c58a4414b
commit
5ac577af19
1 changed files with 59 additions and 21 deletions
|
|
@ -3,9 +3,8 @@ import uuid
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from sqlmodel import select
|
from sqlalchemy import text
|
||||||
from gsap_broker.db import get_session
|
from gsap_broker.db import get_session
|
||||||
from gsap_broker.db_models import AuthorizationContextDB, CompletionReceiptDB
|
|
||||||
from gsap_broker.models import CompleteRequest, CompleteResponse
|
from gsap_broker.models import CompleteRequest, CompleteResponse
|
||||||
from gsap_broker import chronicle
|
from gsap_broker import chronicle
|
||||||
|
|
||||||
|
|
@ -13,27 +12,66 @@ router = APIRouter()
|
||||||
|
|
||||||
@router.post("/complete/", response_model=CompleteResponse, summary="Receive CR (GSAP §5.4)")
|
@router.post("/complete/", response_model=CompleteResponse, summary="Receive CR (GSAP §5.4)")
|
||||||
async def complete(request: CompleteRequest, db: AsyncSession = Depends(get_session)):
|
async def complete(request: CompleteRequest, db: AsyncSession = Depends(get_session)):
|
||||||
result = await db.exec(select(AuthorizationContextDB).where(
|
# Pure raw SQL to avoid all SQLModel/SQLAlchemy greenlet issues
|
||||||
AuthorizationContextDB.context_id == request.context_id,
|
result = await db.execute(
|
||||||
AuthorizationContextDB.status == "authorized"))
|
text("SELECT context_id, status FROM authorization_contexts WHERE context_id = :ctx_id AND status = 'authorized'"),
|
||||||
ac_db = result.first()
|
{"ctx_id": str(request.context_id).replace("-", "")},
|
||||||
if not ac_db: raise HTTPException(status_code=404, detail="AC not found or already consumed.")
|
)
|
||||||
|
ac_row = result.first()
|
||||||
|
if not ac_row:
|
||||||
|
raise HTTPException(status_code=404, detail="AC not found or already consumed.")
|
||||||
|
|
||||||
sig_verified = bool((request.signature or {}).get("value"))
|
sig_verified = bool((request.signature or {}).get("value"))
|
||||||
cr_db = CompletionReceiptDB(
|
cr_id = uuid.uuid4()
|
||||||
id=uuid.uuid4(), context_id=request.context_id, outcome=request.outcome.value,
|
now = datetime.now(UTC)
|
||||||
completed_at=request.completed_at, failure_reason=request.failure_reason or "",
|
|
||||||
chronicle_session_id=request.chronicle_session_id or "",
|
# Insert CR
|
||||||
chronicle_events=request.chronicle_evidence.events,
|
await db.execute(
|
||||||
merkle_root=request.chronicle_evidence.merkle_root or "",
|
text("""INSERT INTO completion_receipts
|
||||||
behavioral_attestation_status=request.behavioral_attestation.status.value,
|
(id, context_id, outcome, completed_at, received_at, failure_reason,
|
||||||
ffc_did=request.ffc.get("did", ""), ffc_signature=(request.signature or {}).get("value", ""),
|
chronicle_session_id, chronicle_events, merkle_root,
|
||||||
signature_verified=sig_verified)
|
behavioral_attestation_status, ffc_did, ffc_signature,
|
||||||
db.add(cr_db)
|
signature_verified, chronicle_event_cid)
|
||||||
ac_db.status = "consumed"; ac_db.consumed_at = datetime.now(UTC)
|
VALUES (:id, :ctx_id, :outcome, :completed_at, :received_at, :failure_reason,
|
||||||
|
:session_id, :events, :merkle_root,
|
||||||
|
:ba_status, :ffc_did, :ffc_sig,
|
||||||
|
:sig_verified, '')"""),
|
||||||
|
{
|
||||||
|
"id": str(cr_id).replace("-", ""), "ctx_id": str(request.context_id).replace("-", ""),
|
||||||
|
"outcome": request.outcome.value,
|
||||||
|
"completed_at": str(request.completed_at),
|
||||||
|
"received_at": str(now),
|
||||||
|
"failure_reason": request.failure_reason or "",
|
||||||
|
"session_id": request.chronicle_session_id or "",
|
||||||
|
"events": str(request.chronicle_evidence.events),
|
||||||
|
"merkle_root": request.chronicle_evidence.merkle_root or "",
|
||||||
|
"ba_status": request.behavioral_attestation.status.value,
|
||||||
|
"ffc_did": request.ffc.get("did", ""),
|
||||||
|
"ffc_sig": (request.signature or {}).get("value", ""),
|
||||||
|
"sig_verified": sig_verified,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update AC status
|
||||||
|
await db.execute(
|
||||||
|
text("UPDATE authorization_contexts SET status = 'consumed', consumed_at = :now WHERE context_id = :ctx_id"),
|
||||||
|
{"now": str(now), "ctx_id": str(request.context_id).replace("-", "")},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Chronicle event
|
||||||
|
cid = await chronicle.emit("GSAP_CR_RECEIVED", {
|
||||||
|
"event_code": "0x2706",
|
||||||
|
"context_id": str(request.context_id).replace("-", ""),
|
||||||
|
"outcome": request.outcome.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update CR with chronicle CID
|
||||||
|
if cid:
|
||||||
|
await db.execute(
|
||||||
|
text("UPDATE completion_receipts SET chronicle_event_cid = :cid WHERE id = :id"),
|
||||||
|
{"cid": cid, "id": str(cr_id).replace("-", "")},
|
||||||
|
)
|
||||||
|
|
||||||
cid = await chronicle.emit("GSAP_CR_RECEIVED", {"event_code": "0x2706", "context_id": str(request.context_id), "outcome": request.outcome.value})
|
|
||||||
cr_db.chronicle_event_cid = cid
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return CompleteResponse(receipt_id=cr_db.id, signature_verified=sig_verified, chronicle_event_cid=cid or None)
|
return CompleteResponse(receipt_id=cr_id, signature_verified=sig_verified, chronicle_event_cid=cid or None)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue