Story 2456: expose the stored DocumentCard

WHY. Blake already generates and stores a document_card.v1 row in context.document_cards. The app had no read door for that row, so the Document Page could not render the card as soon as it landed. This Story adds the read-only route and stops there.

The route

The new app API endpoint is GET /app/documents/{doc_id}/document-card. It mirrors the sibling GET /app/documents/{doc_id}/insight-brief route:

@router.get(
    "/{doc_id}/document-card",
    response_model=DocumentCard,
    responses=AUTH_NOT_FOUND_VALIDATION_SERVER_RESPONSES,
)
async def get_document_card(...):
    return await read_document_card(db, document_id=doc_id)
The response model is the existing contract. The route imports DocumentCard from src.shared.context_contracts. It does not redefine the card, rename fields, or touch the Bug-2451 kind tolerance rules.

The read helper

src/shared/context/card_read.py performs one firm-scoped read from context.document_cards:

SELECT card
FROM context.document_cards
WHERE document_id = :document_id

The app route receives an AppDbDep session, so PostgreSQL RLS supplies the firm boundary, exactly like the insight-brief read door. A missing row raises PramaanNotFoundError with code pramaan.context.document_card_not_found, so a not-yet-generated card returns a clean 404 rather than a 500 or empty payload.

What did not change. The generation path, card ingest path, JSONB shape, assertion kind contract, and observability plumbing are untouched. ADR-124 is satisfied by the existing auto-instrumented app API HTTP handler span; this Story adds no manual span.

Contract and auth surface

SurfaceResult
openapi/app-api.json Adds appGetDocumentCard with a DocumentCard 200 response and canonical error envelopes.
openapi/permission-map.json Maps appGetDocumentCard to matters:read.
openapi/admin-api.json No change; this is an app API route only.

Proof

CheckWhat it proves
tests/contract/test_app_documents_openapi.py The generated schema exposes /document-card, operation id appGetDocumentCard, DocumentCard response model, and ErrorResponse 404 envelope.
tests/integration/test_app_document_card_route.py DB-backed route coverage: no-card 404, stored card round-trip through the contract, and cross-firm 404 through RLS.
scripts/generate_openapi.py and scripts/gen_permissions.py Regenerate producer snapshots for the new route and permission surface.

Story-2456 walkthrough for pramaan-functions. Issue: pramaan-spec#2456.