feat(backend): add operational smoke tooling and safe pricing cleanup endpoints

- add backend README and refresh API map and smoke regression docs
- add full backend smoke regression script
- add admin pricing cleanup preview and dry-run endpoints
- add helper script for test pricing cleanup
- verify typed error contracts, draft flow, publish readiness and preview flows
- verify publish preview retention and clean backend startup behavior
This commit is contained in:
greebo
2026-03-19 22:54:12 +03:00
parent 127c5bff71
commit 0f9c2a1cbd
16 changed files with 551 additions and 235 deletions

View File

@@ -1,6 +1,6 @@
# svg-service backend
Backend for SVG scheme upload, draft editing, pricing, publish preview, and publish lifecycle.
Backend for SVG scheme upload, draft editing, pricing, diagnostics, publish preview, and publish lifecycle.
## Stack
@@ -32,16 +32,15 @@ Default local admin key:
## Core lifecycle
The backend works with a scheme lifecycle:
1. Upload SVG
2. Normalize and persist structure
3. Work in current draft
4. Create / update pricing
5. Build pricing snapshot
6. Inspect publish preview / readiness
7. Publish current draft
8. If editing is needed after publish, create or ensure a new draft again
3. Enter editor flow through context + ensure draft
4. Edit sectors / groups / seats in current draft
5. Configure pricing and inspect diagnostics
6. Build pricing snapshot
7. Inspect publish readiness and publish preview
8. Publish current draft
9. If editing is needed after publish, create or ensure a new draft again
## Main concepts
@@ -49,23 +48,19 @@ The backend works with a scheme lifecycle:
Top-level business entity.
### Scheme version
Concrete version of the scheme.
A version can be `draft` or `published`.
Concrete version of the scheme. A version can be `draft` or `published`.
### Current version
The version referenced by the scheme registry as active current.
### Draft
Editable current version.
All editor mutations and draft pricing operations must target a current draft version only.
Editable current version. All editor mutations and draft pricing operations must target a current draft version only.
### Published version
Non-editable current version.
If current version is published, editor flow must first create or ensure a new draft.
Non-editable current version. If current version is published, editor flow must first create or ensure a new draft.
### Upload artifacts
Stored technical artifacts, including:
- original svg
- sanitized svg
- normalized json
@@ -74,30 +69,23 @@ Stored technical artifacts, including:
## Editor entry flow
Use this flow from frontend or operator scripts.
### 1. Inspect editor state
`GET /api/v1/schemes/{scheme_id}/editor/context`
Response tells whether:
- current version is draft
- editor is available
- a new draft should be created
- recommended action is `use_current_draft` or `create_draft`
### 2. Ensure editable draft
`POST /api/v1/schemes/{scheme_id}/draft/ensure`
Behavior:
- if current version is already draft: returns it with `created=false`
- if current version is published: clones current version into a new current draft and returns it with `created=true`
Returned `scheme_version_id` must be reused as:
Returned `scheme_version_id` should be reused as:
- `expected_scheme_version_id`
for draft reads and mutations.
@@ -105,14 +93,10 @@ for draft reads and mutations.
## Optimistic concurrency
Mutable draft flows support optimistic concurrency through query params:
- `expected_current_scheme_version_id`
- `expected_scheme_version_id`
These guards prevent frontend/editor from mutating a stale draft after another version switch.
Typical typed conflict payload:
Typical typed conflicts:
- `stale_current_version`
- `stale_draft_version`
- `draft_not_editable`
@@ -120,22 +104,19 @@ Typical typed conflict payload:
## Main operator routes
## System
### System
- `GET /healthz`
- `GET /api/v1/ping`
- `GET /api/v1/db/ping`
- `GET /api/v1/manifest`
## Uploads
### Uploads
- `POST /api/v1/schemes/upload`
- `GET /api/v1/uploads`
- `GET /api/v1/uploads/{upload_id}`
- `GET /api/v1/uploads/{upload_id}/normalized`
## Scheme registry
### Scheme registry
- `GET /api/v1/schemes`
- `GET /api/v1/schemes/{scheme_id}`
- `GET /api/v1/schemes/{scheme_id}/current`
@@ -147,8 +128,7 @@ Typical typed conflict payload:
- `POST /api/v1/schemes/{scheme_id}/unpublish`
- `POST /api/v1/schemes/{scheme_id}/rollback`
## Editor / draft
### Editor / draft
- `GET /api/v1/schemes/{scheme_id}/editor/context`
- `POST /api/v1/schemes/{scheme_id}/draft/ensure`
- `GET /api/v1/schemes/{scheme_id}/draft/summary`
@@ -168,8 +148,7 @@ Typical typed conflict payload:
- `PATCH /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}`
- `POST /api/v1/schemes/{scheme_id}/draft/repair-references`
## Pricing
### Pricing
- `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}`
@@ -178,22 +157,19 @@ Typical typed conflict payload:
- `PUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}`
- `DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}`
## Pricing diagnostics
### Pricing diagnostics
- `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`
## Publish preview
### Publish preview
- `POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot`
- `GET /api/v1/schemes/{scheme_id}/draft/publish-preview`
- `POST /api/v1/schemes/{scheme_id}/draft/remap/preview`
- `POST /api/v1/schemes/{scheme_id}/draft/remap/apply`
## Structure read model
### Structure read model
- `GET /api/v1/schemes/{scheme_id}/current/sectors`
- `GET /api/v1/schemes/{scheme_id}/current/groups`
- `GET /api/v1/schemes/{scheme_id}/current/seats`
@@ -202,129 +178,85 @@ Typical typed conflict payload:
- `GET /api/v1/schemes/{scheme_id}/current/svg/display`
- `GET /api/v1/schemes/{scheme_id}/current/svg/display/meta`
## Test mode
### Test mode
- `GET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}`
## Audit
### Audit
- `GET /api/v1/schemes/{scheme_id}/audit`
## Admin / ops
### Admin / ops
- `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`
## Cleanup of test pricing data
Cleanup endpoints are intended for removing diagnostic / test categories accidentally accumulated in a shared scheme.
Preview candidates:
`GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview`
Execute cleanup:
`POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup`
Safety notes:
- use `dry_run=true` first
- keep `delete_only_without_rules=true` unless you intentionally want a harder cleanup
- prefer matching by prefixes instead of raw ids for repetitive test artifacts
Helper script:
- `backend/scripts/cleanup_test_pricing_data.sh`
Example:
`SCHEME_ID=... DRY_RUN=true ./backend/scripts/cleanup_test_pricing_data.sh`
## Typical local flow
## 1. Read current version
Use:
### 1. Read current version
`GET /api/v1/schemes/{scheme_id}/current`
## 2. Ensure draft
Use:
### 2. Ensure draft
`POST /api/v1/schemes/{scheme_id}/draft/ensure`
Store returned:
- `scheme_version_id`
## 3. Read draft state
Use:
### 3. Read draft state
- `GET /draft/summary?expected_scheme_version_id=...`
- `GET /draft/structure?expected_scheme_version_id=...`
- `GET /draft/validation?expected_scheme_version_id=...`
- `GET /draft/compare-preview?expected_scheme_version_id=...`
## 4. Perform editor mutations
### 4. Perform editor mutations
Pass:
- `expected_scheme_version_id={draft_scheme_version_id}`
on every mutation route.
## 5. Inspect pricing quality
Use:
### 5. Inspect pricing quality
- `GET /pricing/coverage`
- `GET /pricing/unpriced-seats`
- `GET /pricing/explain/{seat_id}`
- `GET /pricing/rules/diagnostics`
## 6. Create pricing snapshot
### 6. Build snapshot and inspect readiness
- `POST /draft/pricing/snapshot`
- `GET /draft/publish-readiness`
- `GET /draft/publish-preview?refresh=true`
Use:
### 7. Publish
- `POST /publish?expected_scheme_version_id=...`
`POST /draft/pricing/snapshot?expected_scheme_version_id=...`
## Regression
## 7. Inspect readiness
Main operator regression:
- `backend/scripts/smoke_regression.sh`
Use:
Run:
`API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/smoke_regression.sh`
`GET /draft/publish-readiness?expected_scheme_version_id=...`
## 8. Publish
Use:
`POST /publish?expected_scheme_version_id=...`
## Typed error expectations
Examples of stable typed errors already used in the service:
### Draft concurrency/state
- `stale_current_version`
- `stale_draft_version`
- `draft_not_editable`
### Editor validation
- `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`
- `remap_filter_required`
### Pricing / publish
- `invalid_amount`
- `publish_not_ready`
## Draft summary semantics
`GET /draft/summary` is the compact route for editor bootstrap.
It returns:
- current draft counters
- validation summary
- structure diff summary
- publish readiness summary
This route is intended for frontend side panels / header status / quick preflight.
## Notes
- Draft-only routes must not mutate a published current version.
- Published current version should require `draft/ensure` before any editor mutation.
- Publish readiness can fail even if validation passes, for example when pricing snapshot is missing.
- `api-map.md` and `smoke-regression.md` must be updated together with route changes.