Story: Holding company treasury functionality: FX/CRM visibility, Group Treasury book, consolidated reporting
Table of Contents
This page documents a story in Sprint 25. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
Goal
Acme Corporation Plc (the holding company) behaves like a real holding company in ORE Studio: a treasury user logged in at the holding-company party can see FX rates relevant to its subsidiaries (for remittance and hedging decisions) and has a Group Treasury book for intercompany loans/FX hedges, instead of the party showing a blank Cross-Rates Matrix and having no book/portfolio tree at all. Consolidated group-level risk reporting is scoped/investigated but not necessarily built in this story – see the investigation task and Out of scope.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 25 |
| Now | All six tasks done. Story closed. |
| Waiting on | Nothing. |
| Next | None – story closed. Manual screenshots are a small standalone follow-up, not a new task in this story. |
| Last touched | 2026-08-07 |
Acceptance
- A treasury user logged in at Acme Corporation Plc (the holding party) can open the Cross-Rates Matrix and see live FX rates for its subsidiaries' currencies (GBP/USD/HKD at minimum), without switching party. [DONE – topology + tenant-wide tick consumption, verified live via a 4/4-passed test scenario.]
- Acme Corporation Plc has its own Group Treasury book/portfolio, scoped to parent-level treasury activity (not a trading desk). [DONE – one business unit, one portfolio, two Banking books (intercompany loans, FX hedges), verified live via a 3/3-passed test scenario.]
- The user manual documents the holding company's role and how it differs from the three operating offices. [DONE – new chapter section written, site + PDF both build cleanly; illustrating screenshots deferred to a follow-up (no X access in this session), flagged openly in the chapter via a note callout rather than shipped silently incomplete.]
- Consolidated group-level risk reporting is scoped (findings written up), not necessarily implemented – see the investigation task. [DONE – findings written up, follow-on story recommended, no build committed here.]
Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Define holding company treasury data model additions | DONE | 2026-08-04 | 2026-08-04 | Design task: confirmed the Group Treasury book/portfolio shape (one DIVISION-type unit, one portfolio, two Banking-classified books, all GBP) and decided tenant-wide FX tick consumption for CRM matching over per-party feed bindings. |
| Publish CRM topology for the holding company party | DONE | 2026-08-05 | 2026-08-05 | Holding company now gets majors/exotics/scandies CRM topology during provisioning, same as each office. Getting a clean end-to-end run also required fixing four unrelated pre-existing regressions (asset_class/derivation_kind/scope/binding_mode) and a duplicate-menu bug (stale AdminPlugin build artifact). Verified via DB query and a 4/4-passed Qt client test scenario. |
| Resolve feed-binding party-scoping so holding company CRM can consume FX ticks | DONE | 2026-08-05 | 2026-08-06 | crm_ingest_bridge::update() is now tenant-wide (dropped the party_id_str parameter, feeds every party in the tick's tenant with a matching driver edge). Verified with a new unit test and live: holding company CRM ticks within seconds of opening. Also fixed a second, freshly-introduced regression – a competing PR had "fixed" the same asset_class-validator bug from the opposite end, reverted to match the correct (already-merged) validator fix. |
| Scaffold a Group Treasury book/portfolio for the holding company | DONE | 2026-08-06 | 2026-08-07 | Acme Corporation Plc now has its own Group Treasury business unit, portfolio, and two Banking books (intercompany loans, FX hedges), generated via the seeder pipeline and wired into both db recreate and the live acme_group DQ bundle. Live QA caught a real bug – acme_group was missing the same DQ dataset-dependency registrations the office bundles already had, letting the publish workflow race and leave owner_unit_id null; fixed by adding the missing dependency rows. |
| Investigate consolidated group risk reporting | DONE | 2026-08-07 | 2026-08-07 | Findings only, no build. The party-hierarchy RLS visibility mechanism already lets a holding-party session see every subsidiary's books/trades; risk_report_config has a full ORE parameter surface but zero tests and no Qt UI. Recommend a narrow follow-on story for risk rollup verification+UI; IAS 21 FX translation of subsidiary financials is a separate, much larger consolidated-accounting feature not covered here. |
| Update user manual with the holding company description | DONE | 2026-08-07 | 2026-08-07 | Added a new section to the Acme Corporation manual chapter covering the holding company's FX/CRM visibility and Group Treasury book, contrasted with an operating company's trading floor. Site and PDF both build cleanly with the new content. Screenshots deferred to a follow-up (no X access this session) and flagged openly via a note callout in the chapter rather than shipped silently incomplete. |
Decisions
- Split from the fx_spot_wire_codec_root_cause story's CRM task: that task's original premise (a provisioning bug hiding CRM topology) turned out to already be fixed on main; investigating why the Cross-Rates Matrix was still blank for the default login party surfaced two real bugs (a stale-JWT snapshot in CrmCrossRatesMatrixMdiWindow, and the CRM name picker querying across all visible parties while reload() scopes to only the current party) plus this genuine, larger gap: the holding company has no treasury footprint at all. The two bugs are fixed there; this story covers the holding-company gap.
- Data model design (see the define-holding-company-treasury-data-model
task's Plan for full detail): Group Treasury is one DIVISION-type
business unit (
acme_group.treasury) with one portfolio and two Banking-classified books ("Group Intercompany Loans", "Group FX Hedges"), all GBP – mirroring the existing per-office seeder model in projects/ores.seeder/datasets/acme_corporation/. Feed-binding scoping: FX driver ticks become tenant-wide for CRM-matching purposes (crm_ingest_bridge::update() iterates every party in the tenant, not just the tick's originating feed_binding.party_id) – chosen over giving the holding party its own duplicate feed_binding because FX/IR market data isn't genuinely party-specific, and because each party's own RLS-gated topology config already governs what it's authorized to see, so this doesn't leak anything new. - Implemented the CRM topology publish for the holding company by adding the same market-data plan every office already runs to Step 3 of tenant_handler.hpp's provisioning orchestration. Verifying it live surfaced four unrelated, pre-existing regressions blocking every party's provisioning (not holding-company-specific): a market_series.asset_class validator wired to the wrong reference table, and three DQ-publish SQL functions missing not-null columns (derivation_kind, scope, binding_mode) added by other stories after those functions were last touched. Fixed all four rather than work around them, since they blocked every party equally. Also hit and fixed a duplicate-menu bug (a stale libores.qt.admin.so build artifact from an incomplete AdminPlugin -> IamPlugin rename) blocking manual verification specifically.
- Implemented tenant-wide CRM tick consumption: crm_ingest_bridge:: update() dropped its party_id_str parameter, scanning every party under a tick's tenant (engines_map's std::map ordering makes this a contiguous lower_bound() run, not a full scan) instead of looking up a single (tenant, party) key. Verified live: the holding company's CRM now ticks, fed entirely by the three offices' feeds, with no feed_binding of its own. A new unit test covers the cross-party feed explicitly.
- Live re-verification surfaced a second, unrelated regression: a competing PR (ba638c24d, merged to main independently) had also "fixed" the asset_class-validator mismatch this story's earlier task found (PR #1864), but by changing the DQ-publish writer to FpML-style literals instead of the validator wiring – the opposite end of the same bug, done in ignorance of the already-merged fix. Both landed on main, leaving a validator expecting short codes and a writer emitting FpML names. Reverted the writer back to short codes, matching the validator fix that had already been reviewed.
- Scaffolded the Group Treasury business unit/portfolio/books via the seeder pipeline (generate_data.py/generate_sql.py), not hand-edited JSON/SQL, and wired the new artefact files into both populate.sql (schema-bootstrap order, for db recreate) and acme_bundle_populate.sql (DQ bundle membership, for live provisioning). Live QA in the Qt client caught a bug a DB-only check had missed: both books showed under "(Unassigned)" instead of nested under Group Treasury. Root cause – acme_uk/us/hk each have explicit portfolios->business_units and books->portfolios rows in acme_dataset_dependency_populate.sql (with a comment predicting exactly this failure mode), needed because the DQ bundle-publish workflow orders steps by dependency-graph resolution, not raw display_order; acme_group never needed these before since it only ever published accounts. Added the same two dependency rows for acme_group – fixed, re-verified live.
- Considered adding a third book for profit remittances specifically; decided against it. This system's books represent risk/trading positions, and a remittance is a cash-settlement/equity accounting event, not a priced position – its FX exposure is already covered by "Group FX Hedges" and any intercompany funding leg by "Group Intercompany Loans". Two books matches how a real group treasury centre's risk book structure normally looks.
- Investigated consolidated group risk reporting (findings only, no build): the data-visibility plumbing a consolidated report would need already exists – ores_iam_set_party_context_fn computes a party plus all its descendants into app.visible_party_ids, and every party_isolation_policy RLS policy (books, portfolios, trades) honours it, so a session scoped to the holding party already sees every subsidiary's data. risk_report_config's domain/SQL has a full ORE parameter surface (base currency, NPV/VaR/XVA/SIMM, book/ portfolio scope junction tables with no party constraint), but zero test coverage and no Qt configuration UI – nobody has run one end to end yet, single-party or not. Recommend a narrowly-scoped follow-on story to prove out the risk rollup (verification + the missing UI, not new backend). Separately: IAS 21-style FX translation of subsidiary financials (the story's other stated goal) is a consolidated-accounting concept unrelated to risk-book NPV – a materially different, much larger feature that needs its own scoping if ever pursued, not bundled with risk rollup.
- Documented the holding company in the user manual's Acme Corporation chapter as a new section rather than a separate chapter, since it belongs with "what the group centre itself is" in the chapter's existing narrative arc, not as a standalone topic. Could not capture the two planned screenshots (Cross-Rates Matrix, Group Treasury Org Explorer tree) – no X11 access in this session – so shipped the section text-only with an explicit note callout flagging the gap, rather than either blocking the whole story on screenshot availability or silently shipping incomplete documentation. Verified both manual output formats (site HTML, PDF) build cleanly with the new content regardless.
Out of scope
- Consolidated/group-level risk reporting is investigated, not committed to being built here – see the investigation task, which is expected to spin off its own follow-on story once scoped.