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/tests is 787 passed.
  • check_component_drift.py --all --dry-run reports no drift.
  • check_route_auth_declarations.py passes.
  • ./compass.sh lint passes, and validate_docs.sh reports all 40 components pass.
  • The three parts and their tests compile: ores.http.assets.lib, ores.http.iam.lib, ores.http.refdata.lib and the eight http and storage test executables build clean.
  • ORES_USE_BUSCTL=0 ./compass.sh build fails, and not on this branch: main's dd131c388b deleted ores.dq.api/messaging/fsm_protocol.hpp, and ores.workflow/core/src/service/fsm_state_map.cpp and ores.trading/core/include/ores.trading.core/messaging/trade_handler.hpp still include it. Both files are unchanged from origin/main, and no commit on this branch touches them.

Exceptions, recorded rather than done:

  • storage keeps its three hand-written routes. They are file operations on {bucket}/{key}, not entity CRUD, so there is no entity to generate from. variability keeps 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.

Emacs 29.3 (Org mode 9.6.15)