Task: Scaffold story: Generate HTTP routes from the models
Table of Contents
This page documents a task in the Generate HTTP routes from the models story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
The gateway's routes generate from the components' models instead of being hand-written. A codegen facet projects an entity or an operation model onto a route unit, the generated route reaches its service over the canonical NATS protocol, and every route states one authentication position.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Generate HTTP routes from the models |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-27 |
3. Acceptance
- A codegen facet generates a route unit per entity, with a header, an implementation and a test, from the entity's model.
- The generated route speaks the canonical NATS protocol and carries the caller's session, so the service's own permission check governs access.
- Every route states its authentication position by construction, declared in the model rather than by hand at the call site.
- The hand-written route files for assets, iam and the misnamed risk group are deleted, and their endpoints still answer.
4. Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
5. Notes
6. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario)
that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
7. PRs
| PR | Title |
|---|---|
| #2210 | [http] Generate the gateway's routes from the component models |
8. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Generated code calls build() twice per route |
projects/ores.codegen/library/templates/ores.cpp.http-route.route_implementation.org, ores.cpp.http-route.operation_implementation.org and their tangled .mustache files |
Fixed | The route implementation archetypes now build once into a local named after the route ({{identifier}}_built) and pass that value to both add_route() and register_route(). Both take const domain::route&, so the local costs no copy. The templates were re-tangled and 15 generated files were regenerated: three in ores.http/assets, eleven in ores.http/iam and one in ores.http/refdata. The check_route_auth_declarations.py gate resolves the built local back to its builder, so it still reads each route's authentication position; two tests pin the new shape. Regenerating refdata also rewrote seven unrelated files carrying main's later work, and those were restored. |
| 2 | The storage client's documentation overstates token enforcement | projects/ores.storage/core/include/ores.storage.core/net/storage_transfer.hpp and .../net/http_client.hpp |
Fixed | The wording now says the token is a required parameter, that an empty token is accepted, and that the service refuses the request with 401. No throw was added, because the compute wrapper passes an empty token: the node holds no credential, so a throw would break the tree build. nats_client::bearer_token makes no non-empty claim, so it is unchanged. |
9. Result
The gateway's routes generate from the components' models. The codegen facet
ores.cpp.http-route projects an entity or an operation model onto a route
unit, as three entity archetypes (header, implementation, test) and three
operation archetypes, tangled into six mustache templates. A message opts in
with :http_route: true; absence means the gateway does not expose it, so an
unmarked message and :http_route: false are the same refusal.
Three components landed, each as its own generated part beside a hand-written aggregator, and each with its hand-written route file deleted rather than kept in parallel:
| Part | Model source | Generated routes |
|---|---|---|
ores.http.assets |
image, tag, image_tag | 29 |
ores.http.refdata |
currency | 10 |
ores.http.iam |
account, role, permission, session, login_info, and the operation groups | 53 |
The deleted files are core/.../routes/{assets,iam,risk}_routes.{hpp,cpp};
risk_routes was a misnomer for the refdata currency group.
The gateway is a protocol gateway. A generated route builds the canonical
request, sends it over NATS and maps the reply to an HTTP response, so it
depends on the target's wire protocol and not on its service library. The
server no longer links ores.assets.core.lib, ores.refdata.core.lib or
ores.iam.core.lib; it links the three generated parts. With the
hand-written storage, variability and information routes, the gateway serves
99 endpoints; 92 of them are generated.
Security is one declaration and one enforcement point. The template emits
.auth_required() or .auth_optional() from the model. build() and
add_route() throw std::logic_error when a route states neither, so a
route cannot be public by omission, and
projects/ores.codegen/scripts/check_route_auth_declarations.py fails the
build if any registered route is silent. The route forwards the caller's
bearer token, so the service's own permission check governs the real caller.
The route template builds each route once into a local named after the route
({{identifier}}_built) and passes that value to both add_route() and
register_route().
The storage client takes its caller's bearer token as a required parameter, and every in-repo caller passes one.
Verification, on the rebased base cc4a9aedaf:
ctest -R 'http|storage'is 8 of 8.pytest projects/ores.codegen/testsis 787 passed.check_component_drift.py --all --dry-runreports no drift.check_route_auth_declarations.pypasses../compass.sh lintpasses, andvalidate_docs.shreports all 40 components pass.- The three parts and their tests compile:
ores.http.assets.lib,ores.http.iam.lib,ores.http.refdata.liband the eight http and storage test executables build clean. ORES_USE_BUSCTL=0 ./compass.sh buildfails, and not on this branch: main'sdd131c388bdeletedores.dq.api/messaging/fsm_protocol.hpp, andores.workflow/core/src/service/fsm_state_map.cppandores.trading/core/include/ores.trading.core/messaging/trade_handler.hppstill include it. Both files are unchanged fromorigin/main, and no commit on this branch touches them.
Exceptions, recorded rather than done:
storagekeeps its three hand-written routes. They are file operations on{bucket}/{key}, not entity CRUD, so there is no entity to generate from.variabilitykeeps its one hand-written route; it has no models.- The trade and market series export handlers carry the reporting service's token, not the end user's, because the reporting service calls them without delegation. The compute wrapper holds no JWT, so its storage transfers still receive 401.
- A trailing
{key}GET shadows the versions path beneath it. Pre-existing and worth its own fix.