Task: Validate a saved document on the server
Table of Contents
This page documents a task in the Make every configuration screen follow one standard story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Move the checks in the web's check() to the write path, so a client cannot
save what the dialog refuses and every client gets the same answer. The errors
move; the warnings and information findings stay on the screen and do not block
a save.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Make every configuration screen follow one standard |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-25 |
3. Acceptance
- The analytics handler refuses a pricing engines document that breaks a rule, before it writes a row, and reports every problem it found.
- Each problem carries a code from
{name_missing, no_products, model_missing, engine_missing, parameter_repeated, parameter_value_missing}, the product id when it is about a product, the setting's scope and name when it is about a parameter, and a short message. - The save response carries the problems beside
successandmessage, and the message counts them. - The BFF answers a refused save with 400 and the problems in the body. A version conflict stays 409 and a permission refusal stays 403.
- The web dialog names the problems in a person's words, one line each.
- The warnings and information findings of
check()stay on the screen.
4. Plan
The protocol is generated from the operation model, so the problem struct and the field on the save response were added there and the C++ and TypeScript protocols regenerated.
The rules live in one pure function,
ores::analytics::validation::problems_of, which the pricing engines save
calls before it hands the document to the service. The handler also serves
today's market documents, and each kind has its own subject and its own save,
so only the pricing engines save is validated.
The BFF turns the problems into invalidRequest details, and the web turns
them back into the findings check() produces, so one set of translated words
says the same problem whichever side found it.
5. Measurements
| What | Value | Command | Commit |
|---|---|---|---|
| Analytics handler suite | 12 cases, 93 assertions | ores.analytics.core.tests "[configuration_document]" |
8bf09e806e |
| Analytics core suite | 76 cases, 75 pass | ores.analytics.core.tests |
8bf09e806e |
| BFF pricing suite | 18 tests | npx vitest run packages/bff/src/pricing-engines.test.ts |
8bf09e806e |
| Web pricing suites | 62 tests | npx vitest run packages/web/src/pricingEngines |
8bf09e806e |
| Latest pricing rows in the seeded data | 23 configurations, 517 products, 1256 settings, none breaking a rule | compass sql |
8bf09e806e |
6. Notes
- The BFF reads a name before the service does, so a blank name is refused
there: the server's
name_missingrule is proven by the handler suite rather than through the BFF. read_all_pricing_model_product_parameter_historyin the analytics core suite fails on a freshly recreated database, and no commit on this branch touches it: the numeric value-set guard added at f1c17918b8 refuses the synthetic value with_v2appended.
7. Test Scenarios
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| #3836 | [analytics] Validate a pricing engines document before it is written |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
A document with a product that names no model is refused through the BFF with
400 and {"code":"invalid-request","message":"The document has 1 problem.","problems":[{"code":"model_missing","productId":"...","message":"The Swap product has no model."}]}.
A document whose product states the same setting twice and leaves its value
empty is refused with 400 and two problems, parameter_repeated and
parameter_value_missing, each naming the product, the scope and the setting.
A valid document creates with 201 and amends with 200. A refused save writes no
configuration header and no pricing row.