Files
svg-backend/backend/docs/frontend-integration-contract.md
greebo 54b36ba76c chore(backend): finalize backend baseline and frontend handoff contract
freeze the current backend contract for frontend integration

document the stabilized backend surface and handoff expectations
mark the current state as the baseline for further frontend work
2026-03-20 16:46:24 +03:00

12 KiB

Backend Integration Contract

This document is the frontend handoff contract for the svg-service backend. It is written as an integration baseline, not as an internal backend README.

1. Base URL and Auth

  • Base URL: http://<host>:9020
  • API prefix: /api/v1
  • Auth header: X-API-Key

All non-/healthz routes require an API key.

Auth failure contract:

  • missing API key -> 401 with string detail: Missing API key
  • invalid API key -> 403 with string detail: Invalid API key
  • valid non-admin key on admin-only route -> 403 with string detail: Admin role required

2. Roles and Access Boundaries

  • admin
    • full access to protected routes
    • required for all /api/v1/admin/... routes
  • operator
    • allowed on non-admin protected routes
    • denied on admin-only routes
  • viewer
    • allowed on non-admin protected routes
    • denied on admin-only routes

Frontend implication:

  • admin UI must treat admin routes as optional capabilities gated by role
  • frontend must not assume operator or viewer can call cleanup, audit, backfill, or current-artifact admin routes

3. Core Entities

Upload

Represents one uploaded SVG source and its normalized/sanitized artifacts.

Important fields:

  • upload_id
  • original_filename
  • content_type
  • size_bytes
  • original_storage_path
  • sanitized_storage_path
  • normalized_storage_path
  • normalized_elements_count
  • normalized_seats_count
  • normalized_groups_count
  • normalized_sectors_count

Scheme

Top-level business object created from upload.

Important fields:

  • scheme_id
  • source_upload_id
  • name
  • status
  • current_version_number
  • published_at

Scheme Version

Versioned snapshot of the scheme structure and publish state.

Important fields:

  • scheme_version_id
  • scheme_id
  • version_number
  • status
  • normalized_storage_path
  • normalized_*_count

Sector

Structure entity in a specific scheme_version.

Important fields:

  • sector_record_id
  • sector_id
  • element_id
  • name

Business identity priority:

  • use sector_id when present
  • fallback to element_id
  • never treat sector_record_id as business identity across versions

Group

Important fields:

  • group_record_id
  • group_id
  • element_id
  • name

Business identity priority:

  • use group_id when present
  • fallback to element_id
  • never treat group_record_id as business identity across versions

Seat

Important fields:

  • seat_record_id
  • seat_id
  • element_id
  • sector_id
  • group_id
  • row_label
  • seat_number

Business identity priority:

  • use seat_id when present
  • fallback to element_id
  • never treat seat_record_id as business identity across versions

Pricing Category

Important fields:

  • pricing_category_id
  • scheme_id
  • name
  • code

Price Rule

Important fields:

  • price_rule_id
  • scheme_id
  • pricing_category_id
  • target_type
  • target_ref
  • amount
  • currency

Artifact

Artifact registry row for generated backend files.

Important fields:

  • artifact_id
  • artifact_type
  • artifact_variant
  • storage_path
  • status
  • meta_json

Important artifact types currently exercised by regression:

  • sanitized_svg
  • normalized_json
  • display_svg
  • publish_preview

4. Lifecycle State Machine

Fresh Upload

Flow:

  1. POST /api/v1/schemes/upload
  2. backend creates:
    • upload
    • scheme
    • initial scheme_version
    • structure rows
    • initial artifacts

Expected initial state:

  • scheme.status = draft
  • scheme.current_version_number = 1
  • current version status = draft

Current Draft

If current scheme/version is still draft:

  • editor works directly against current version
  • draft/ensure is idempotent
  • draft/ensure returns created=false

Ensure Draft From Published Current

If current scheme/version is published:

  • POST /api/v1/schemes/{scheme_id}/draft/ensure
  • backend creates a new draft version
  • current pointer switches to the new draft
  • version number increments

Publish

Preconditions:

  • current scheme is draft
  • current version is draft
  • publish readiness must be satisfied

Publish path:

  1. optional draft/pricing/snapshot
  2. GET draft/publish-readiness
  3. optional GET draft/publish-preview
  4. POST /api/v1/schemes/{scheme_id}/publish

Expected result:

  • scheme becomes published
  • current version becomes published

Rollback

Path:

  • POST /api/v1/schemes/{scheme_id}/rollback

Effect:

  • current pointer switches to requested historical version_number
  • scheme returns to draft
  • target version becomes current editable draft

Unpublish

Path:

  • POST /api/v1/schemes/{scheme_id}/unpublish

Effect:

  • current scheme becomes draft
  • current version becomes draft

5. Editor Flow

Entry Point

  • GET /api/v1/schemes/{scheme_id}/editor/context

Use it first to decide whether:

  • current draft can be edited directly
  • or a new draft must be created from published current

Important response fields:

  • current_scheme_version_id
  • current_version_number
  • scheme_status
  • scheme_version_status
  • current_is_draft
  • create_draft_available
  • recommended_action

Draft Read Models

  • POST /api/v1/schemes/{scheme_id}/draft/ensure
  • GET /api/v1/schemes/{scheme_id}/draft/summary
  • GET /api/v1/schemes/{scheme_id}/draft/structure
  • GET /api/v1/schemes/{scheme_id}/draft/validation
  • GET /api/v1/schemes/{scheme_id}/draft/compare-preview

Frontend should treat draft/structure as the main editable read model.

Patch Operations

Supported flows:

  • single seat patch
  • bulk seat patch
  • sector create/patch/delete
  • group create/patch/delete
  • repair references
  • remap preview/apply

Frontend rule:

  • always send expected_scheme_version_id when mutating or reading draft state after editor entry

Stale Conflict Handling

If backend returns a stale or draft editability conflict:

  • stop optimistic local mutation flow
  • re-read:
    • editor/context
    • draft/summary
    • draft/structure

Do not keep editing against stale cached scheme_version_id.

6. Pricing Flow

Categories

  • GET /api/v1/schemes/{scheme_id}/pricing
  • POST /api/v1/schemes/{scheme_id}/pricing/categories
  • PUT /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}
  • DELETE /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}

Rules

  • POST /api/v1/schemes/{scheme_id}/pricing/rules
  • PUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}
  • DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}

Read Models

  • GET /api/v1/schemes/{scheme_id}/pricing
  • GET /api/v1/schemes/{scheme_id}/pricing/coverage
  • GET /api/v1/schemes/{scheme_id}/pricing/unpriced-seats
  • GET /api/v1/schemes/{scheme_id}/pricing/explain/{seat_id}
  • GET /api/v1/schemes/{scheme_id}/pricing/rules/diagnostics
  • GET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/price
  • GET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}

Frontend rule:

  • empty pricing on a fresh upload is valid
  • do not treat categories=[] and rules=[] as backend failure

7. Publish Flow

Main endpoints:

  • POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot
  • GET /api/v1/schemes/{scheme_id}/draft/publish-readiness
  • GET /api/v1/schemes/{scheme_id}/draft/publish-preview
  • POST /api/v1/schemes/{scheme_id}/publish

Frontend sequencing rule:

  1. ensure draft
  2. mutate if needed
  3. create/refresh pricing
  4. build pricing snapshot
  5. read publish readiness
  6. read publish preview if UI needs preview surface
  7. publish

8. Admin/Ops Flow

Admin-only endpoints:

  • GET /api/v1/admin/schemes/{scheme_id}/current/artifacts
  • GET /api/v1/admin/schemes/{scheme_id}/current/validation
  • POST /api/v1/admin/schemes/{scheme_id}/current/display/regenerate
  • POST /api/v1/admin/display/backfill
  • GET /api/v1/admin/artifacts/publish-preview/audit
  • POST /api/v1/admin/artifacts/publish-preview/cleanup
  • GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview
  • POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup

Healthy publish-preview audit contract:

  • orphan_files_count = 0
  • missing_files_for_db_rows_count = 0
  • db_rows_count == disk_files_count

Frontend implication:

  • admin tools must not be shown as generally available functionality
  • admin cleanup/destructive flows must be role-gated on the client and still handle backend 403

9. Typed Error Catalog

Auth

  • 401 string detail: Missing API key
  • 403 string detail: Invalid API key
  • 403 string detail: Admin role required

Lifecycle / Draft / Publish

  • stale_draft_version
  • stale_current_version
  • current_version_inconsistent
  • draft_not_editable
  • publish_not_ready

Editor Uniqueness / References

  • editor_uniqueness_error
  • editor_reference_error
  • duplicate_seat_id
  • duplicate_seat_id_in_payload
  • duplicate_sector_id
  • duplicate_group_id
  • duplicate_sector_element_id
  • duplicate_group_element_id
  • unknown_sector_id
  • unknown_group_id
  • unknown_sector_ids
  • unknown_group_ids
  • unknown_target_sector_id
  • unknown_target_group_id
  • business_identifier_nullification_forbidden

Pricing / Remap / Test

  • invalid_amount
  • remap_filter_required
  • test_preview_failed

Validation Report Codes

These appear inside validation report payloads rather than as top-level HTTP conflict codes:

  • duplicate_seat_ids
  • missing_seat_contract
  • seats_without_sector_or_group
  • seats_without_price

Frontend rule:

  • do not parse only HTTP status
  • always inspect structured detail.code when detail is an object

10. Frontend Obligations

  • always handle auth failures 401 and 403
  • always handle stale/conflict responses on draft, publish, and lifecycle operations
  • never treat *_record_id as stable cross-version business identity
  • always prefer business ids:
    • seat -> seat_id, fallback element_id
    • sector -> sector_id, fallback element_id
    • group -> group_id, fallback element_id
  • re-read current/draft state after:
    • any 409
    • publish
    • rollback
    • unpublish
    • draft/ensure returning a newly created draft
  • do not assume current version remains stable across concurrent operator sessions
  • do not assume publish-preview artifacts or display artifacts are frontend-owned resources

11. Non-Persistent Assumptions Frontend Must Avoid

The frontend must not assume that these remain stable forever:

  • scheme_version_id
  • seat_record_id
  • sector_record_id
  • group_record_id
  • artifact storage_path
  • publish-preview cache artifacts

These are safe to treat as business-stable:

  • scheme_id
  • version_number within one scheme
  • seat_id when present
  • sector_id when present
  • group_id when present

12. Known Limitations / Deferred Tech Debt

  • some lifecycle negative contracts still return mixed styles:
    • typed object conflicts for 409
    • plain string details for some 404 and auth cases
  • validation warnings and error code families are not yet unified into one single global error envelope
  • admin/ops routes are backend-internal tools, not end-user product APIs
  • corruption remediation smoke exists only for publish_preview, not for every artifact type

13. Regression Baseline Frontend Can Rely On

The frontend can rely on the following regression-backed flows:

  • fresh upload on clean DB
  • current/draft/editor read flow
  • editor mutations and stale draft protection
  • pricing setup and publish flow
  • version lifecycle:
    • publish
    • ensure draft from published current
    • rollback
    • unpublish
  • admin ops:
    • audit
    • cleanup
    • destructive pricing cleanup for safe fixture categories
  • full admin permission matrix on implemented admin endpoints
  • controlled publish_preview corruption detection and remediation
  • negative upload validation
  • negative auth matrix
  • negative lifecycle matrix

For normal editor work:

  1. authenticate
  2. upload or pick scheme_id
  3. read editor/context
  4. call draft/ensure if needed
  5. read draft/structure
  6. mutate using current scheme_version_id
  7. on 409, reload editor state before retry
  8. configure pricing if needed
  9. create pricing snapshot
  10. read publish readiness / preview
  11. publish

For admin UI:

  1. verify admin role in client auth state
  2. call admin endpoints
  3. still handle backend 403
  4. treat cleanup and remediation as explicit operator actions, not background automation