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

529 lines
12 KiB
Markdown

# 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
## 14. Recommended Frontend Integration Sequence
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