Task: Close M06, the synthetic TypeScript twin
Table of Contents
This page documents a task in the Implement the synthetic market data journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Every synthetic protocol header has its TypeScript twin committed, both sides generated from an org model, and the twin-coverage check covers the component.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Implement the synthetic market data journeys in ores.web |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-09 |
3. Acceptance
- Every synthetic protocol header has a committed TypeScript twin: 12 protocol
modules under
packages/wire-protocol/src/generated/synthetic/protocol/and the 9 domain modules they import. - Every one of those twins is generated from an org model. The four non-entity operation surfaces are operation models, so no hand-written protocol header remains in the component.
check_protocol_twin_coverage.pypasses with synthetic among the covered components.check_component_drift.py --component syntheticregenerates every address and reports no drift, so a twin cannot go stale silently.
4. Plan
Three steps, in the order the gates forced them.
4.1. Generate the entity twins
The nine entity models produce 9 protocol twins and, because each protocol twin
imports its entity's interface, the ores.ts.domain facet produces the 9 domain
twins they name. The protocol twin alone does not compile: the domain facet is
part of the same job.
4.2. Model the four operation surfaces
Committing the twins is what makes the twin gate cover the component, and the
gate then demands a twin for every *_protocol.hpp, not only the entity
ones. Four hand-written headers had none:
feed_config_protocol.hpp, simulate_fx_spot_paths_protocol.hpp,
simulate_ir_curve_paths_protocol.hpp and preview_ir_curve_shape_protocol.hpp.
They are the operation surfaces the clean standard's P04 recorded as staying
hand-written. The estate has two precedents, and iam is the other one: its
non-entity operation surfaces are operation models
(ores.iam.*_operations.org, ores.iam.*_messages.org) whose C++ header and
TypeScript twin both generate.
We took the iam precedent. Modelling them is the only way both sides generate from one source, and a hand-written TypeScript twin would have mirrored a hand-written C++ header with nothing keeping the two in step. This supersedes the P04 exception for this component.
parameter_spec is carried by both the simulate and the preview request, and a
payload type is resolved inside its own protocol, so it cannot be declared in
two models without either duplicating the C++ struct or leaving the TypeScript
side naming a type no module exports. The two IR operations therefore share one
model, ores.synthetic.ir_curve_operations, and one generated header.
The batch bounds stay out of the protocol. max_simulate_num_ticks,
max_simulate_num_paths and max_preview_curve_entries are the service's
clamps, not wire fields, so simulate_paths_common.hpp remains a hand-written
header and the request structs no longer alias two of them as unused members.
**Migrate the consumers and regenerate
Eight consumer files re-point at the merged header, component_files.cmake is
regenerated from the tree scan, and the two superseded headers are deleted.
5. Measurements
| What | Value | Command | Commit |
|---|---|---|---|
| TypeScript modules generated for synthetic | 9 domain + 12 protocol = 21 | check_component_drift.py --component synthetic |
f9bb337f17 |
| Synthetic protocol headers | 12, all generated | ls projects/ores.synthetic/api/include/ores.synthetic.api/messaging/*_protocol.hpp |
f9bb337f17 |
| Protocol twin coverage | 418 headers in 18 components, no model opts out | check_protocol_twin_coverage.py |
f9bb337f17 |
| Drift at every synthetic address | no drift | check_component_drift.py --component synthetic |
f9bb337f17 |
| Synthetic suites | 4 of 4 pass | compass test run -- -R synthetic |
f9bb337f17 |
| Twins the scaffold predicted | 9, protocol only | check_component_drift.py --component synthetic --address ores.ts.protocol --dry-run |
1df1a9af30 |
6. Notes
- The scaffold's acceptance was wrong twice, and both corrections are worth
keeping. It said nine twins, but the request that proved it named only the
ores.ts.protocoladdress: the domain facet is required too, because a protocol twin imports its entity's interface. - The scaffold also named the wrong blocker: it said the models needed the
* Physical spaceaddress mechanism before their TypeScript could be generated. They do not, and a targeted run created all nine entity twins from the models as they stood. - The drift gate needed no change.
_committed_ts_facetsscopes a TypeScript facet by whether its output directory exists, so committing the twins putsores.ts.domain,ores.ts.protocolandores.ts.trade_typeinto the address set on their own. - An earlier claim that the drift gate cannot see untracked generated files was
wrong. It diffs the tree with
git diff, but it also records the untracked set before regenerating and fails on anything regeneration materialises (check_component_drift.py, "untracked file(s) materialized by regeneration"). The gate that let M06 hide is the twin gate's scoping, not this one: both gates agree there is nothing to check until a component's first twin lands. component_registry.pycarried a stale paragraph saying synthetic "is not listed yet" while the tuple lists it. Corrected here to record the M06 closure and the four items still open.- clever_dijkstra owns the clean-standard record,
task_clean_synthetic.org, and its M06, P04, V05 and V06 rows still read as open or held. The file is on main, so the rows were struck here rather than handed over in a message: M06 is closed, P04 records that the operation surfaces are modelled now, and V05 and V06 drop the twin from their open lists. This is the one file this branch edits that another environment owns. - V06's row claimed
COMPONENTS_UNDER_TESTstays("iam",), which is false: the tuple carriessynthetic. Corrected to say that the registry lists a component with four items still open. That disagreement is a gate question in its own right – the registry asserts every item passes while the record says four do not, and nothing cross-checks the two – but it is out of scope here. - Three gate failures surfaced on the merged tree and none was synthetic's.
formatwas the one exception: the new line inir_curve_preview_handler.hppneeded clang-format. The other two came from79e1123446("Serve the sub-periods coupon type list"), which added a refdata entity but left the hand-written composition points behind: the service event registrar and the shell refdata composite each needed one include and one call. That leftcodegen-testsred on two cases, and the same refdata work had leftores_schema.{refdata,trading}.pumland the index stale. All three were fixed on this branch, because the story cannot land through a red gate and each fix is the one the check names. The diagrams were regenerated without the timestamp-only churn the generator writes into all 22 files, and the SVG redraws for diagrams whose source had not changed were left alone because the render is not byte-stable.
7. Test Scenarios
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| https://github.com/OreStudio/OreStudio/pull/2991 | [ores.synthetic] Close M06, the synthetic TypeScript twin |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
Complete. 21 TypeScript modules are committed under generated/synthetic/: 9 domain twins and 12 protocol twins, all generated from the models. The four non-entity operation surfaces are operation models now, so no hand-written protocol header remains. The drift gate and the twin gate both cover the component.
Synthetic speaks TypeScript. 21 generated modules are committed: the 9 domain
interfaces and the 12 protocol modules, each protocol module the twin of a
generated C++ header. The four hand-written operation headers are gone, replaced
by three operation models from which both sides generate, and the two IR
operation surfaces share one model so parameter_spec is declared once. The
drift gate regenerates every address and reports no drift, and the twin gate now
covers the component. The M06 item on the clean standard is closed; P04 is
superseded for synthetic.
Verified on the merged tree: the whole tree builds, compass test run --
-R synthetic passes 4 of 4 (ores.synthetic.api, .core, .service and
ores.shell.synthetic), check_protocol_twin_coverage.py reports 418 headers
in 18 components with no opt-out, and check_component_drift.py --component
synthetic reports no drift. Three gate failures on the merged tree were fixed
here; two were trunk's from 79e1123446, and the third was a clang-format
tweak.