Protocol types are generated from the C++ codegen model

Table of Contents

1. Status

Accepted, and implemented. The ores.ts.protocol and ores.ts.domain facets exist, and their output is committed under packages/wire-protocol/src/generated/.

2. Context

The TypeScript client must encode msgpack bodies whose keys are the exact C++ member names the codec writes. A single renamed field desynchronises the client silently: the server cannot decode the request, and because the C++ IAM handlers do not reply when decoding fails, the client sees a thirty second timeout rather than an error.

Hand-writing those names in TypeScript makes every protocol change a two-place edit with no compiler help across the language boundary.

3. Decision

Extend the existing code generator, which already renders the C++ structs from the org entity model, to also render the TypeScript protocol types and their validation schemas from that same model.

The org files are the single source of truth. Each entity under projects/<component>/**/modeling/*.org already declares :type:, :cpp_type:, :nullable:, and :primary_key: per column, and codegen renders book_protocol.hpp from it. A TypeScript target renders beside it from the same input, so the two cannot disagree.

The generated artefact lives under packages/wire-protocol/src/generated/. It is committed, and the drift gate covers it.

4. Consequences

The generator covers the codegen-managed entities and the operation models. The TypeScript projection writes one protocol module per entity, junction, or operation model, and one domain module per entity or junction, under packages/wire-protocol/src/generated/{component}/.

The authentication surface is modelled, and it is not hand-written.

Not every protocol header is modelled. 16 of the 251 *_protocol.hpp files carry no AUTO-GENERATED banner. Six are in marketdata: crm_protocol.hpp, curve_republish_protocol.hpp, curve_snapshot_protocol.hpp, import_protocol.hpp, market_feed_config_protocol.hpp, and market_series_export_protocol.hpp.

5. The authentication protocols

login_protocol.hpp and account_protocol.hpp are generated files: each carries the AUTO-GENERATED banner. Their operation models are projects/ores.iam/modeling/ores.iam.login_messages.org and projects/ores.iam/modeling/ores.iam.account_messages.org. Their TypeScript twins are under packages/wire-protocol/src/generated/iam/protocol/.

The account aggregate is modelled by the entity model projects/ores.iam/modeling/ores.iam.account.org, and the generator renders account.ts under packages/wire-protocol/src/generated/iam/domain/. The knowledge page projects/ores.iam/core/modeling/iam_account.org is not the model.

6. Considered alternatives

Add a codegen model type for operations. The tree took this alternative. An ores.codegen.operation model expresses subject-scoped request and response pairs with arbitrary field lists. 29 models use it, under projects/<component>/modeling/, across 12 components. The ores.ts.protocol facet admits the operation model type, so each model renders a TypeScript module as well as a C++ header. The entity model cannot express these shapes: it always emits offset and limit fields, so it cannot render an empty refresh_request, and its reply shapes are fixed.

Emit JSON Schema from C++ reflection. A reflection library's schema output already works in this tree, and every message type is reflectable because the handlers decode them. A small binary that walks an explicit list of message types and prints JSON Schema would be the absolute single source of truth and would cover the hand-written protocols without refactoring them. It costs more C++ work and a build step.

Hand-write the TypeScript schemas. Rejected. It is not the current state: ores.codegen renders the schemas from the same models, and hand-writing is the largest ongoing drift risk.

7. Current implementation

ores.codegen renders the TypeScript with the ores.ts.protocol and ores.ts.domain facets. The templates are projects/ores.codegen/library/templates/ores.ts.protocol.org and ores.ts.domain.org, and the output is committed under packages/wire-protocol/src/generated/.

scripts/emit_protocol_ir.py is a separate tool, run by the workspace script ir:protocol. It emits a language-neutral protocol IR for the entity CRUD messages, and it supports --all and --check.

Emacs 29.3 (Org mode 9.6.15)