Kairo — Fund Data Assurance Platform
User Manual for Operations Users
Audience: Operations end users — inside Kairo and at SaaS client organisations. All roles (Viewer, Analyst, Operator, Administrator). Not covered: infrastructure, deployment, APIs, or developer topics. Version: July 2026 (platform v0.1.0).
Part I — Welcome & Getting Started
1. What is Kairo?
Kairo takes fund data that arrives from many asset managers in many different formats, cleans and reconciles it into one trusted golden record, and delivers it out to the destinations that need it — with a full audit trail and quality monitoring at every step.
The platform is organised around two flows:
- Inbound — files come in (upload, email, SFTP, API, or web scraping), get mapped to the industry-standard Openfunds format, and become golden data.
- Outbound — golden data goes out to destinations (Bloomberg, Morningstar, client platforms) in each destination's required format, on schedule or on demand.
Between the two sits the assurance layer: quality scoring, discrepancy triage, human review queues, reconciliation against destination copies, and a complete audit trail. AI agents assist at every step, but a human always confirms anything uncertain — this "human-in-the-loop" (HITL) principle runs through the whole platform.
2. Signing in and your session
Signing in. Open the platform URL and you land on the Sign in page. Enter your email and password. There is a show/hide eye icon on the password field.
Error messages you may see:
| Message | What it means | What to do |
|---|---|---|
| "Invalid email or password" | Wrong credentials | Check and retry |
| "Account temporarily locked. Try again in 15 minutes." | Too many failed attempts (10 within 15 minutes) | Wait ~15 minutes, or ask your administrator to reset your password |
| "Too many requests. Try again later." | Too many login attempts from your network in a short burst | Wait a minute and retry |
| "Session expired" | Your sign-in lapsed while you were working | Just sign in again — nothing is lost |
Forgot your password? There is no self-service reset. Contact your administrator — they set a new password for you from the Users admin page. New accounts are also created by administrators only; there is no self-registration.
How long does a session last? Your sign-in is refreshed quietly while you work; if it can't be refreshed you're returned to the login page with "Session expired". Sessions never outlive 7 days without a fresh login. (The "Keep me logged in for 30 days" checkbox does not currently extend this.)
3. Finding your way around
The sidebar — two modes
The left sidebar has a Platform mode (day-to-day operations) and an Admin mode (configuration and administration). A small round button next to the brand logo toggles between them: a person icon takes you into Admin mode; a back-arrow returns you to the platform. You only see the toggle if your role includes at least one admin-area page.
Platform mode navigation:
| Section | Pages |
|---|---|
| (top) | Dashboard |
| Data | Explorer · Golden Record · Time Travel |
| Files | Upload · Ingested · Delivered · Documents |
| Quality | Data Quality · Lookup Review · Tenant Review · First Production Check · Structural Conflicts · PPC Reconciliation |
| Pipelines | Inbound Pipes · Outbound Pipes · Audit Trail |
Admin mode navigation:
| Section | Pages |
|---|---|
| Delivery | Destinations · Subscriptions · Email Channels |
| Configuration | Policies · Policy Stream · Extraction Config · Doc Templates · Template Profiles · Openfunds Registry · AI Usage |
| Administration | Clients · Products · Users · Roles · AI Models · Custom Fields · TC Parameters · Branding · Quarantine · Danger Zone |
| Platform | Back to Dashboard |
Which sections you see depends on your role (Part III). The Administration group and several Configuration pages are restricted to platform super-administrators.
Other sidebar items: a green Upload data button (shortcut to Upload), an Asset Managers link in the footer, a Light/Dark appearance toggle, your profile card (initials, email, role chip), Sign out, and the app version bottom-right. Signing out also clears your working Asset Manager choice and the command palette's recent pages from this browser, so the next person who signs in here starts clean; panel widths and the theme stay.
The working Asset Manager picker
At the top of the screen, next to the notification bell, sits the AM picker — the single most important navigation control on the platform. It selects the Asset Manager you are currently working on, and almost every page filters to it: dashboard metrics, file lists, pipes, quality queues, documents, reconciliation, audit trail.
- Click it to open a searchable list; type to filter, Enter picks the first match.
- Choose All asset managers to see everything you have access to.
- If your account only has access to one AM, the picker shows as a fixed label.
- Pages with their own AM dropdown stay in sync with the picker — changing either updates both.
Tip: if a page looks emptier than you expect, check the AM picker first. On the Audit Trail, selecting a specific AM also hides platform-level events (admin actions) — switch to "All" to see those.
The notification bell
The bell (top-right) is your alert inbox: run failures, files quarantined, schema drift, missing NAVs, expiring documents, delivery failures. The badge colour shows the worst unread severity — red (critical), amber (warning), blue (info).
- Click an alert to mark it read and jump straight to the affected item.
- Mark all read clears the list — for you. Reading an alert is personal: marking it read hides it from your bell and from nobody else's, so a colleague, or a user at another Asset Manager who sees the same platform-wide alert, still sees it until they read it themselves. An alert disappears for everyone only when the platform resolves it — the condition it described has cleared (prices landed, the file matched) — and a resolution clears it for the Asset Manager whose condition cleared: on a shared generic template, your data landing never closes another Asset Manager's still-open alert about theirs. If the same condition fires again, the alert comes back for everyone who had dismissed it, with its repeat count. Repeats collapse within one Asset Manager only: on a shared generic template, another Asset Manager's run failure or schema drift never lands in your alert, and yours never lands in theirs. Alerts about one Asset Manager's data — run failures, run errors, schema drift, data-quality findings — that were recorded before ownership was stamped carry no Asset Manager and are shown to platform administrators only.
- The bell follows the AM picker. With an Asset Manager selected, the dropdown and the badge count are that Asset Manager's alerts plus platform-wide notices, which carry a small platform tag so they are never read as the selected Asset Manager's. Another Asset Manager's alerts never appear under the one you have selected — a run failure belongs to the Asset Manager whose data the run carried, even on a shared generic template — and a legacy alert that carries no Asset Manager is shown to administrators only when no Asset Manager is selected ("All asset managers").
- Mute an AM: hover an alert and click mute to silence that Asset Manager's non-critical alerts (useful for a known-noisy feed). Critical alerts always come through, muted or not. Review and unmute under "Muted asset managers" at the bottom of the dropdown.
- Repeated identical alerts collapse into one entry with a "×N" count — a feed failing 50 times shows once, not 50 times.
Pulse — your one-click briefing
The floating violet Pulse button (bottom-right; you can drag it anywhere) opens a slide-over briefing: an AI-written summary of platform status, items needing attention, recent activity, and an overview — generated live and refreshable. While it writes you'll see a small thinking indicator with an elapsed-time counter, and the text streams in as it's generated. It's the fastest way to answer "what state is the platform in right now?" The briefing covers only the asset managers you can see — pipelines, runs, files and deliveries are scoped to your access, exactly like every other screen; a platform administrator sees the whole estate. It also follows the AM picker: with an Asset Manager selected, the briefing (and Refresh) covers that Asset Manager alone — its name is shown in the panel header — exactly what a user of that Asset Manager would read, so estate-wide counts that cannot be attributed to one Asset Manager (scraped documents, discrepancies) are not included; choose "All asset managers" for the estate view. On a deployment that does not run the web-scraping service, the estate view's scraped-document and discrepancy counters read zero (there is nothing to count) while the data-event and entity counters stand — one unavailable counter never blanks the others.
The command palette (⌘K)
Press ⌘K (Mac) or Ctrl+K (Windows/Linux) anywhere in the app to open the command palette — type a few letters of any page name and press Enter to jump straight there. It only offers pages your role can actually see (the same rules as the sidebar), lists your recently visited pages first when the search box is empty, and includes quick actions such as Open Pulse briefing. Navigate with ↑/↓, open with Enter, close with Esc.
4. Reading the screens
The platform uses one consistent colour language everywhere:
| Colour | Meaning |
|---|---|
| Emerald / green | Good — locked, completed, resolved, active, valid |
| Amber | Needs attention — warning, pending review, expiring |
| Red | Problem — failed, error, material issue, expired |
| Blue | In progress or informational (running states pulse) |
| Violet | AI-related, or "in review" |
Confidence bars appear wherever the AI has proposed something (mappings, extracted values, golden-record fields): emerald = high confidence (roughly 80–90%+), amber = medium, red = low. Low-confidence items are exactly where your review time is best spent.
Status chips on pipes: draft/building (being set up) → review (needs human attention) → locked (production-ready, read-only) → possibly failed. Locking is always reversible and never loses history.
Reference codes: every run has a friendly reference — INB-0000042 for inbound runs, OUT-0000017 for outbound deliveries, EXT-… for document extractions. Use these when discussing an issue with colleagues or support; they appear in run histories and the Audit Trail.
Part II — Key Concepts
You don't need to memorise these — each is explained where it appears — but this glossary is the vocabulary the whole platform (and this manual) uses.
Asset Manager (AM). The fund house whose data you are handling. Every file, entity, pipe, and query belongs to exactly one AM. The platform never mixes AMs' data: if it cannot confirm who something belongs to, it refuses rather than guessing. In the app, the AM picker does the narrowing for you — every page that lists AM-owned data sends the selected Asset Manager to the server and shows that Asset Manager's rows alone (shared platform shapes such as generic templates stay visible), and a slow response for the previously selected Asset Manager is never painted under the new one; if you call the API directly, every listing that returns AM-owned records (pipes, runs, files, events, documents, receipts…) accepts an asset_manager_id filter with one platform-wide contract: it narrows within what your token can already see, platform-shared shapes (generic templates, platform-wide ignores) stay visible, and asking for an AM you cannot see is refused with a 403 — never answered with an empty list that reads as "no data". Leave the filter off to see everything your token can see. A few older endpoints (core /events, the scrape listings) also keep their legacy am_id spelling as an alias; passing both spellings with different values is refused rather than one silently winning. More broadly, the API refuses any query parameter an endpoint does not declare: a typo like ?asset_manger_id= is a 400 that names the parameter and suggests the closest declared one — never a silently unfiltered success (platform operators can soften this per service to log-and-pass during an integration's transition window).
Pipe (pipeline). A configured route for data. An inbound pipe brings one source's files in and maps their columns to the standard. An outbound pipe formats golden data for one destination. A run is one execution of a pipe against one file or delivery.
Pipe lock. When an inbound pipe is finished, you lock it. Locking captures the file's "signature" — its column count, a fingerprint of its headers, and its wrapping format. From then on, only files matching that exact signature flow through the pipe. This is deliberate: a brand-new file shape can only enter the platform through the manual Upload screen, where a human reviews it. Files arriving automatically (email/SFTP/API) that match no locked pipe are quarantined, never silently processed. Locking = "a human approved this shape."
Openfunds & OF field IDs. The fund-industry data standard everything is mapped to (~1,800 fields). Each field has a code whose prefix tells you the category: OFST = static data (e.g. OFST020000 = ISIN), OFDY = time-series (e.g. OFDY000035 = NAV), OFPM = portfolio manager, OFPH = holdings. Click any OF code in the app to see its full definition. A handful of static fields are per-country and the registry lists them once as a template ending in XX (OFST6030XX Country Legal Registration, OFST6010XX Country Registration Date …): the real code replaces XX with the country's ISO alpha-2 code — OFST6030DE is Country Legal Registration (DE) — and the platform resolves every such code from its template everywhere (mapping targets, the field browser, the golden record, delivery). A suffix that is not a country (OFST6030ZZ) is refused with the cure named.
Entity hierarchy. Fund data is a tree, not a flat table: Company → Umbrella → Fund → Share Class, with sub-entities hanging off funds and share classes — valuations (NAV/AuM per date), holdings, portfolio managers, ratings, country registrations, benchmarks, listings, dividends, fee schedules. Flat source files are automatically split so each field lands on the right level.
Data events & the golden record. Every value observed from every source is stored permanently as a "data event". The golden record is the single trusted current value per field per entity, computed from all events by a policy:
- Last Wins — the most recently received value wins (good for NAVs/prices).
- Source Priority — sources are ranked; the highest-ranked source with a value wins (good for static data, e.g. a KID outranks a factsheet).
You consume the winner; the disagreement remains visible in provenance.
The three time axes. Every observation carries three coordinates: effective date (the business date it relates to), received at (when Kairo ingested it), and source. Time Travel lets you fix any of these to answer questions like "what did we know about 31 March, as of 1 April?"
HITL — human-in-the-loop. Anywhere the AI proposes something uncertain — a mapping, a canonical value, document metadata, a structural change — it queues for a human to confirm. Data keeps flowing on the AI's tentative answer, but a person always has the final say.
Lookups & canonical values. Sources use their own labels ("Retail" vs "R", country names vs ISO codes). A lookup maps a raw value to the one canonical value. Unknown values are AI-proposed and queued in Lookup Review for you to confirm, override, or reject. Lookup tables are scoped — pipeline, Asset Manager, or global — and the scope is always an explicit choice: a table created for one client's feed belongs to that client (its contents are client data — account numbers, fund codes), while global is reserved for genuinely universal vocabularies (countries, currencies, languages). Reads respect the same boundary: you see global tables plus your own Asset Managers' only — and a pipeline-scoped entry belongs to your Asset Manager even when the pipe is a shared generic template, so another tenant running the same template never loads it. Writing at global scope is a platform-administrator act — a global entry is applied to every Asset Manager's data during normalisation, so only platform admins can confirm or add one; everyone else confirms at pipeline or AM scope (inside their own tenant).
Quarantine. The holding area for automatically-arrived files that matched no locked pipe. Each held file carries a plain-English reason and resolution actions (retry, rebuild, build new pipe, reject).
Drift. When a feed's shape changes slightly (a column added/renamed/removed), its files stop matching and quarantine as a drift candidate — a near-match close enough that the right fix is to rebuild the existing pipe from the new file rather than build a new one. A drift alert (the dashboard's Attention panel, the pipe page, the bell) belongs to the Asset Manager whose file raised it: on a shared generic template you see your own alerts only — another tenant's file names and column or sheet names never appear — you can dismiss your own, and a clean file of yours never closes another tenant's alert. Platform administrators see every alert, including legacy ones recorded before ownership was stamped.
FPC — First Production Check. When a pipe first produces a new share class, it is held in a review queue until a human eyeballs its golden record and approves it. This is the highest-value manual check on the platform — the difference between "looks built" and "actually correct". On the outbound side the gate reports its own health: every run records whether the FPC check was enforced or errored — and a pipe that enforces FPC refuses to deliver when the gate cannot be verified (with a critical alert), rather than shipping share classes it could not check.
PPC — destination read-back reconciliation. A destination (Bloomberg, Morningstar…) holds its own copy of the data you sent. A PPC read-back is that copy, ingested back so Kairo can compare it against the golden record — scoped to the fields actually delivered to that destination. The party to chase for a mismatch is always the destination, never the Asset Manager. A read-back never feeds the golden record.
Quality score & delivery readiness. Each entity gets a completeness score (0–100) and a hard delivery-readiness verdict with blocking reasons (missing identifier, missing required fields, missing parent link). On outbound, a readiness gate can withhold not-ready entities from a delivery.
Documents: lineage, versions, permalinks, validity. A logical document (e.g. "the German KID for this share class") is a lineage; each re-upload with new content is a new version (old versions are archived, never deleted). A permalink is a stable URL that always serves the latest public version and can be rotated (old URL dies instantly). Validity windows (e.g. a KID is valid ~12 months) are inferred per document type and human-confirmable; expiring documents raise alerts. Every document is restricted by default — nothing is public unless deliberately made so.
Custom fields. Facts with no Openfunds home — defined per AM (AM_42:product_code) or platform-wide (PLATFORM:internal_risk_band) and mappable like any OF field.
Decomposition & multi-value groups. Some files pack several logical records into one row (a row describing a fund and its share class and a benchmark), or several values into one cell (DE,AT,CH = three country registrations). Decomposition and multi-value groups split these correctly — proposed by the AI, confirmed by you.
Part III — Roles: what you can see and do
Access is organised into six sections, each grantable as read (view) or write (change). Write always includes read.
| Section | Covers | Unlocks in the sidebar |
|---|---|---|
| Explorer & Data | Fund Explorer, Golden Record, Time Travel | Data group |
| Files & Documents | Upload, Ingested, Delivered, Documents | Files group |
| Inbound Pipes | Inbound pipelines: mapping, execution, audit | Inbound Pipes, Audit Trail |
| Outbound & Delivery | Outbound pipes, destinations, subscriptions, delivery | Outbound Pipes; Delivery admin group |
| Data Quality | Discrepancies, lookups, PPC, production checks | Quality group |
| Administration | Clients, users, roles, configuration | Configuration & Administration groups |
The five standard roles
| Role | In one sentence | Grants |
|---|---|---|
| Administrator | Full access to every section | Read + write on all six sections |
| Operator | Runs the platform day-to-day, no administration | Read + write on Explorer, Files, Inbound, Outbound, Quality |
| Analyst | Views everything operational and works the quality queues | Read on all five work sections, plus write on Data Quality |
| Viewer | Read-only | Read on the five work sections |
| Client | The oversight seat — an outside party watching their own service, read-only | Read on Explorer, Files, Outbound, Quality (no inbound machinery) |
What this means in practice:
- A Viewer can browse the Explorer, golden records, files, pipes, quality dashboards and audit trail — and change nothing.
- A Client sees the same facts the service desk acts on, live and read-only, for their own asset managers only (tenancy rides the account's AM grants, not the role). Their home screen is the Publication Board — the day's plan, states and countdowns — with the Explorer, delivered files and quality dashboards behind it. They shape their own alerts and can have the close-of-day ledger emailed (see §6.21). The inbound pipe machinery is deliberately out of their view.
- An Analyst can additionally act on quality items: resolve discrepancies, confirm/override/reject lookup proposals, approve/reject First Production Checks and structural conflicts, and resolve PPC diffs. They cannot upload files, edit pipes, or configure delivery.
- An Operator can do everything operational: upload files, build and edit pipes, lock/unlock/execute, manage destinations, subscriptions and deliveries, plus all quality work. They do not see the admin areas.
- An Administrator additionally manages users, roles, clients, AI configuration and platform settings.
Two further notes:
- Super-administrator is a platform-level flag above roles (typically Kairo platform staff). Super-admins see everything, including pages ordinary Administrators do not (Users, Roles, Clients, Danger Zone, Quarantine actions, brand switching, destructive operations).
- A user with no role assigned can log in but sees essentially nothing. Access is deliberately opt-in: your administrator must assign you a role.
Your role chip is shown on your sidebar profile card. If a page or button you expect is missing, ask your administrator to check your role — don't assume the feature doesn't exist.
Part IV — Your AI assistants
Kairo is agent-first: most pages carry an AI panel, each with a name and a specialty. All agents share the same rules — they only see the Asset Managers you're allowed to see, they answer from real data and real logs (when you ask "why did this fail?", they quote the actual error), they never invent numbers, and every AI call is recorded in the Audit Trail with its cost. Chat agents remember roughly the last 10 turns of your conversation.
The chat panels share a common language: while an agent works you see a small animated thinking indicator with a live elapsed-time counter, and each reply carries a small timing note (how long the answer took). Where an agent acts (Hermes), the actions it actually took appear as status chips under the reply; where an agent suggests follow-up questions (Chronos), they appear as tappable chips under the latest answer — clicking one sends it.
The autonomy policy — how far the agents may go
For three routine action types — resolving an unknown value to its canonical, retrying a quarantined file when its pipe changed after the file arrived, and accepting additive-only schema drift (columns added, none removed, every existing mapping survives) — you set a line per action on Admin → Agent Autonomy: observe (log only), propose (prepare the work, stop for a human), or act with notice (apply after a hold window unless someone holds it). Acting is gated on two more axes. The agent's confidence must clear your threshold where a confidence score exists — that is the lookup action; the retry and drift actions record no confidence at all, because their gate is the condition itself (the pipe changed after the file arrived; the drift is additive-only), so the threshold does not apply to them and the screen disables that input for those rows. In every case the decision's blast radius (share classes across the publications it feeds) must sit inside your ceiling — an unknowable blast radius always stops at propose. An asset manager can set its own line, tighter than the platform's. With an asset manager selected in the top bar, the Agent Autonomy screen shows a second card, This asset manager's own line: a lower level, a higher confidence bar, a smaller blast ceiling or a longer hold than the platform line — never looser (a looser value is refused naming the platform value). The agent resolves each decision against the tenant's own line when it has one and the platform line otherwise, both when it decides and again when it applies, so tightening your line during a hold window holds your own queued decisions and nobody else's. Remove returns the asset manager to the platform line. Setting an own line needs Administration write access for that asset manager; the platform line stays a platform administrator's.
Every decision is recorded with the agent as the actor, its confidence, its reasoning and the exact policy that authorised it; each raises a bell notice ("auto-applies at HH:MM unless held") with a one-click Hold during the window and a one-click Reverse after application (the prior state is restored — a promoted lookup is un-promoted and re-queued, a widened signature is narrowed back). Hold and Reverse follow the access rule of the data the decision touches: you act on decisions for the Asset Managers you administer, and a platform-level decision — one tied to no single Asset Manager, such as retrying an unassigned quarantined file or a global-scope promotion — is held or reversed by a platform administrator only (the same rule that keeps global lookup scope admin-only). Silent auto-apply is deliberately unavailable, and data judgements — approving a share class for production, accepting a structural change, rejecting a source's value — cannot be expressed in this policy at all. They stay human. That is a feature, not a gap.
The notice is not decoration — it is the consent the mode is named for, and the engine treats it that way. A decision only stays scheduled when its bell notice was actually recorded: if the notice cannot be delivered, the decision is saved as proposed instead and waits for a human like any propose-level item (agent-decision notices also ignore per-AM mutes — you can mute routine alerts, never the "the agent is about to act" warning). The policy is re-checked at the moment of application, not just when the decision was made: tighten a line — level, confidence threshold, or blast ceiling — while something is in its hold window and it is held with the reason recorded ("policy tightened after decision"); dropping a level below act-with-notice immediately holds everything scheduled under it. And Reverse means what it says: reversing a quarantine retry finds the run that retry actually created, reverts it through the same path the runs page uses, and returns the file to quarantine — if that run cannot be resolved yet (still executing, or it failed), the reversal is refused with the reason instead of being recorded as done.
| Agent | Where | What it does for you |
|---|---|---|
| Pulse (Kairos) | Dashboard + Pulse button | Auto-generated platform health briefing: status, needs-attention items, recent activity. Read-only; you don't prompt it. |
| Atlas | Inbound pipe detail | Builds the initial column→Openfunds mappings when a pipe is created, then works with you in chat to refine them: edit or create mappings, write transform rules, propose lookup entries. Won't touch a locked pipe. |
| Pipeline Chat | Inbound pipe detail (chat panel) | The conversational side of Atlas — "map column 5 to ISIN", "skip the empty columns", "why is this value failing?" |
| Nexus | File handling (admin) | Suggests which Asset Manager an unidentified file belongs to (1–3 candidates with confidence and reasons). Suggestion only — a human confirms. |
| Oracle | Fund Explorer | Natural-language fund data queries: "show NAV history for this ISIN", "which share classes are in EUR", "top holdings by weight". Read-only; results highlight in the Explorer. Oracle works from a bounded sample of the book (the deepest entities, plus whatever your question names — an ISIN, or a field like "currency", whose full per-entity values are pulled in complete). It knows it holds a sample: when the answer isn't in view it says "not in my visible sample — narrow the question" rather than claiming the platform lacks the data. |
| Chronos | Golden Record & Time Travel | Explains the golden record (values, sources, confidence, conflicts) and answers point-in-time questions across the three time axes. Suggests follow-up questions. Read-only. |
| Sentry | Data Quality | Quality intelligence: open discrepancies, material issues, patterns, delivery readiness. Read-only. |
| Argus ("The Watcher") | Ingested files, Documents, Data Quality, Inbound Pipes | File/document/pipeline intelligence: who uploaded what and how it arrived, formats, extraction and run results — quoting real error messages. Read-only. |
| Hermes ("The Courier") | Outbound, Delivery, Delivered files | The delivery agent — both an intelligence panel and an actor: it can create/rename/delete destinations, add delivery targets, link/unlink pipes, change scope, set filename patterns, set or clear schedules, and execute deliveries — all from chat. Actions show as ✓ chips and everything is permission-checked and audited. |
| Scope Agent | Subscriptions | Turns plain English ("all active EUR UCITS share classes domiciled in LU") into structured delivery filters you can preview and apply. |
| Mentor ("The Guide") | Help page | Answers "how do I…?" questions about the platform itself, grounded in this user guide — with a pointer to the relevant section. Role-aware: if the action you're asking about needs access you don't have, Mentor says which. Read-only. |
| Sentinel | Audit Trail | Activity intelligence: "what failed today?", "show AI costs by agent", "who ran pipelines this week?" |
| Doc Review Agent | Document review page | Verifies and corrects AI-extracted document fields; can re-read specific pages of the source PDF on request. |
| Lookup Proposer | Background | You never chat with it — when a pipeline hits an unknown value, it proposes a canonical mapping which appears in Lookup Review for you to confirm. |
A practical habit: before hunting through tables, ask the page's agent. "Any failed deliveries today?", "Which pipelines have low confidence?", "Show me all files for Meridian this week" — the agents are usually the fastest route to an answer, and they link you to the right records.
Part V — How-to guides
These are the day-to-day procedures. Each states who can perform it (minimum role).
5.1 Onboard a new Asset Manager end-to-end
Who: Operator (Administrator for channel provisioning). Hands-on time: ~1–2 hours for a straightforward AM; 3–6 hours for a multi-file, multi-entity AM.
Phase 0 — Intake (before touching the platform). Collect from the AM: a representative sample file (real headers, 20+ rows); format and any wrapping quirks; frequency (daily/weekly/monthly); full snapshot vs delta; the entity scope (funds, share classes, holdings, ratings…); whether rows carry multiple entities; whether files arrive as linked sets per batch; the inbound channel (SFTP/email/API); and delivery destinations. Two questions always worth asking up front: which column is canonical when two could carry the same fact? and what are production volume, frequency, and out-of-scope items? A missing answer here is the most common cause of a half-built pipe.
Phase 1 — Bootstrap (~5 min). Go to Upload, select (or create) the AM, and upload the sample. Format, delimiter, header row, and wrapping are auto-detected; a multi-sheet Excel becomes one pipe per sheet. Click Build Pipeline — Atlas maps the columns and the pipe lands in review.
Phase 2 — Mapping review (the real work, 30 min–3 hrs). On the pipe detail page, work through the mappings (see 5.2 steps 4–6). Focus on low-confidence rows, enum warnings, and any decomposition/dependency proposals.
Phase 3 — Lock (~1 min). Lock the pipe. From now on, only files matching this signature will flow through it.
Phase 4 — First execute + First Production Check (10–20 min). Execute against the sample, then verify in the Explorer: does the entity tree look right? Check the Quality Badge (score, readiness, blocking reasons). Then complete the First Production Check on the new share classes (5.5) — eyeball the golden record for "garbage on row one": wrong entity level, unparsed dates, values on the wrong currency.
Phase 5 — First delivery (20 min–1 hr, if applicable). Build the outbound side (5.4): destination, outbound pipe, validation, link, schedule, execute, confirm in Delivered.
Phase 6 — Go live on the real channel. Switch from "emailed sample" to automatic arrivals: create an inbound transfer source (Admin → Sources — SFTP/FTPS with per-directory filters, post-fetch rules and a poll schedule; see 7.4), or register an inbound email channel (Admin → Email Channels; the email subject can route to a pipe by name), or have the AM send via API. Anything arriving must match the locked signature or it quarantines — which is exactly the safety you want. (Note: PGP-encrypted inbound files are not yet supported.)
Phase 7 — Steady state. Watch the notification bell, the Data Quality overdue panels, and the Audit Trail. Mute a known-noisy AM's non-critical alerts if needed.
5.2 Upload a file and build an inbound pipe
Who: Operator.
- Upload. Go to Upload, pick the Asset Manager (or leave generic for a shared industry template like an EET/EPT), and drag the file in. Supported: CSV, TSV, XLSX, XLSM, legacy XLS, JSON, XML, PDF. (A legacy .xls is read with its own reader and shows format “XLS” in the file list — old vendor price files work as-is, no re-saving as .xlsx. A macro-enabled .xlsm is read exactly as an .xlsx — many asset managers publish static and reference data from macro-driven workbooks, so there is no need to strip macros or re-save before uploading; macros are never retained or run by the platform, but see "Macros and malware" below for what to know before opening such a file on your own machine.) There is an upload size limit of 50 MB, the same limit that applies to emailed attachments; a larger file is refused outright rather than partly stored. If the auto-detected header row is wrong, click "Wrong header? Pick a different row" and select the true header. A file is not always "a header row and then data": some master-file templates carry two header rows — field ids on the first (
OFST010035…) and descriptive labels on the second ("LEI Of Umbrella" …), with data from the third — or a units row, a banner between the header and the first record, a subtotal line. Read as data, such a row becomes a junk record keyed by a label. The platform handles all of these with one row layout on the pipe, in 0-based source rows: the header row (the column names — the pipe's signature), data starts at (rows between the header and it are not data), ignored rows (dropped wherever they sit — subtotals, notes, a repeated header) and evidence rows (a labels row, a units row: never data, never part of the signature, but shown to Atlas as context for each column). The two-row header is simply header 0, evidence row 1, data from row 2. When the platform recognises the shape it offers it under the detected headers — Treat row 1 as evidence, data from row 2 (per sheet on a multi-sheet workbook) — and never decides on its own. Accept it, or declare the layout yourself, and the pipe you build inherits it. From then on the excluded rows are left out of the data on every run, the evidence rows reach Atlas beside each column's id, and later arrivals of the same shape match the pipe and run without any manual step. Files that arrive by SFTP, email or API never pass this screen — so the layout, header row included, is also editable on the pipe page after the build (see the Row layout card in 6.13); saving it rebuilds the pipe. An XML file is flattened to a table automatically: the platform picks the repeating record element (the row entity — one row per share class, per charge, per data point…), copies parent fields onto every row, and names columns after the plain field names — a document's namespace declarations never leak into the headers. If it picked the wrong level, the pipe's detail page shows an XML row entity card listing every candidate it found — pick a different one and the table reshapes (e.g. one row per charge instead of one per share class). This works even for documents where every candidate looks like a typed value entry — the platform still offers the structured repeating element rather than refusing to tabulate the file. Repeated entries that name themselves through an attribute —<Charge name="AnnualManagementCharge">with a value and a valid-from date inside each — become one column per entry name (Charge_AnnualManagementCharge_Value,Charge_AnnualManagementCharge_ValidFrom, …), so one row per share class still carries every charge; a charge type missing from one share class simply leaves that column blank. When a file restates the same entry several times, the one with the newestValidFromdate wins — never simply the last one in the file, which in real feeds is often a stale restatement. Small single wrapper blocks whose children are plain fields (a<Cap>holdingClassCap/PrevailingCap, say) are unpacked onto the row as well (Cap_ClassCap,Cap_ClassCap_AppliesTo, …) — only blocks that genuinely occur once under their parent, and never the record element itself: a repeated record (another share class in the same list, say) is never unpacked onto its sibling's row, so a row only ever carries its own values plus true parent-level context. The column layout a file was analysed with is pinned alongside the pipe, so later files of the same feed — even sparse ones carrying a single entry — always flatten to the same columns the mappings were built against. - Match or build. If a locked pipe already matches, you'll be offered Execute with <pipeline> — one click and you're done. Otherwise click Build Pipeline. (PDF/Word documents route to AI extraction instead — see 5.7.)
- Wait for the build. Atlas maps every column to an Openfunds field. Wide files take a few minutes. The pipe opens in review. The mapping table then shows a row for every source column: a column Atlas could not (or chose not to) map arrives as a Skip row — often with its reasoning and suggested near-matches — rather than disappearing, so nothing in the file is ever silently out of reach; un-skip it and pick a target if you need it. This holds on every build path, including each sheet's pipe of a multi-sheet workbook. A file laid out one column per country (the Openfunds flat layout — columns headed
OFST6030IE,OFST6031IE,OFST6011IE… across many countries) maps each column to that country-specific code: a header that is the code is mapped deterministically, and Atlas may propose the concrete code for a column named in words (“Registered for sale in Germany” →OFST6030DE). Each value lands under its own code on the share class, with the template's accepted values (yes/res/no) enforced. The narrow layout — one row per country with the country in its own column — is a different shape and is offered as a Country Registration multi-value group instead (5.10c). - Wait for the build. Atlas maps every column to an Openfunds field. Wide files take a few minutes. The pipe opens in review. The mapping table then shows a row for every source column: a column Atlas could not (or chose not to) map arrives as a Skip row — often with its reasoning and suggested near-matches — rather than disappearing, so nothing in the file is ever silently out of reach; un-skip it and pick a target if you need it. When Atlas maps two columns to the same field, the lower-confidence column arrives as a Skip row too, its reasoning naming which column won — re-map it by hand if it is the better source. This holds on every build path, including each sheet's pipe of a multi-sheet workbook.
- Review the mappings. Click any mapping row to open the Mapping Inspector — the single most useful screen on the pipe. It shows the source samples, the proposed Openfunds target with its accepted values, a plain-English description of any transformation, a live preview running your actual file rows through the mapping, and Atlas's reasoning with click-to-apply alternatives. Work down the table prioritising red/amber confidence rows and ⚠ enum flags.
- Fix what needs fixing. You have four tools, use whichever fits:
- Ask Atlas (chat): "map column 3 to ISIN", "skip columns 10–14", "combine first and last name into one field".
- Manual edit: pick a different OF target from the dropdown; mark rows Reviewed; toggle Skip on columns you don't want.
- Rule builder (Inspector → Edit → Build transform rule): author conditional, fallback-chain, concatenate, computed, or dispatch logic with a form — no code — and preview against real rows before saving. Invalid rules are rejected on the spot. Each shape is explained with examples in 5.9 Transform rules.
- Fix picker: any enum-violating value in the live preview gets a Fix → button — map the raw value to a valid canonical once, and it applies to all rows and all future runs.
- Set the entity model. In the "What does each row represent?" card, confirm the entity type (Share Class / Fund / Valuation / etc.), the entity-key column (usually ISIN), parent column, and date/currency columns for time series. For a Valuation or Dividend pipe the currency column is the price currency of each row — leave it empty only when every class in the file publishes in a single currency: the platform then takes each class's registered currency (its Topic, see §5.10a) and stamps every event as assumed, and it refuses the rows of a class that publishes in more than one currency, naming them, rather than guess. The AI pre-fills the whole card — parent column and date/currency dimensions included — on every build path, so each sheet's pipe of a multi-sheet workbook (a NAV-history tab, say) arrives with its time-series model already proposed: confirm it here rather than reconstructing it by hand. For unusual shapes, use Preview to see exactly how many entities would be created before saving. If one row carries several entities, apply the decomposition proposal; if the AM sends linked file sets, set up batch sequencing so dependent files wait for the master.
- Lock. Click Lock Pipeline. Locking is reversible (Unlock) and never loses history. If the pipe was built from a file that is sitting in quarantine (the usual route from the Quarantine page, or a file an earlier execute refused), locking executes that file straight away and the confirmation says so — there is no need to click Execute as well; a click while that run is still in progress simply shows the same run.
- Execute. In the Execute section, pick the file, optionally Preview (dry run, writes nothing), then Execute Pipeline. Watch progress live in Run History. Both preview and execute read real file contents, so both are scoped like every other read: you can only run a file belonging to an asset manager you have access to (files not yet attributed to one — fresh uploads and quarantined arrivals — stay usable), and the pipe's own asset manager is checked as well. A file outside your access is refused rather than read, whichever pipe you run it through — which also means a run can never land another asset manager's values in your book. Very large files are fine: for straightforward pipes (no reshape, bounded block, cross-level operation or document fetch) the run reads the file in bounded slices rather than loading every row into memory, so a six-figure-row spreadsheet executes in a steady footprint — the processed count climbs live, with the total appearing once the last row is read. Pipes that do use those features still load the whole table; for a multi-year backfill through one of them, split the file into slices (a year at a time, say) and execute each — re-sending overlapping rows is safe, because an unchanged value is recorded as a confirmation of the existing one, never a duplicate.
- Verify. Open the entity in the Explorer: check the tree, values, and Quality Badge. Then complete the First Production Check for any new share classes.
Macros and malware
Two separate checks run on everything the platform receives, and it is worth knowing which is which because they behave differently.
Macro carriers are flagged, not blocked. Macro-enabled workbooks (.xlsm, and legacy .xls/.doc files carrying a VBA project) are legitimate and common, so the platform accepts them and reads them normally. It does not retain or run the macros — the reader never loads them. What it does do is mark the file: the file row shows ⚠ contains macros. That warning is about your own machine, not the platform's. Downloading the file gives you the original bytes, macros and all, and it is your spreadsheet application that decides what to run when you open it. The check is structural, not based on the file's name — a macro workbook renamed to .xlsx is still flagged.
Malware blocks. Files stored in the cloud are scanned for malware. A file the scanner clears is unremarkable and shows nothing. A file the scanner flags is quarantined automatically: it is moved to the separate quarantine bucket, its row shows ⛔ malware detected, and any attempt to download it is refused with the reason. The file is retained rather than deleted — you will usually want to tell the sender, and deleting the evidence first makes that conversation harder.
Scanning happens just after a file is stored rather than before, so a newly-arrived file has no verdict for a short window. A file that arrived by email and matched a pipe does not run automatically during that window: its automatic execution is held until the scan answers, the file row says so in plain words, and the pipeline runs by itself the moment the verdict clears. Holding is never losing — if no verdict arrives within half an hour, the platform concludes that no scanner covers that file, records it as unscanned (never as clean) and proceeds, so an installation without a scanner behaves exactly as it did before. A file the scanner calls infected is never executed, at any point.
Scanning applies to cloud storage — a local development installation shows every file as unscanned rather than pretending it is clean. Either way the platform never claims a file is clean when it does not know.
What arrives by email is checked twice over. Anyone who knows an inbound address can send to it, so an attachment is accepted only when its extension is one the platform handles and the bytes themselves are that kind of file. A program renamed to .csv, or a spreadsheet name wrapped around something else, is refused and named in the log — the sender's own description of the file is never enough on its own.
5.3 Files arriving automatically — and what to do about quarantine
Who: Operator (quarantine actions require admin access).
Once a pipe is locked and a channel is live, files flow with no human involvement: arrival → signature match → auto-execute → golden record update → (optionally) event-driven delivery. You only get involved when something doesn't match.
When a file quarantines, a deduped alert appears in the bell linking to Admin → Quarantine. Each held file shows its channel, the AM, a plain-English reason, and — when the shape is close to an existing pipe — a violet "Near-match N%" drift badge with a panel listing exactly which columns were added (green +) and which are missing (red −) versus the nearest pipe.
Choose one of four actions:
| Situation | Action |
|---|---|
| The pipe was fixed/locked after the file arrived | Retry match — re-runs the matcher; on success the file executes automatically. On failure it shows a side-by-side signature comparison against the closest pipes. |
| The feed's shape drifted (near-match badge shown) | Rebuild <pipe> — one click rebuilds the existing pipe from this file: surviving columns keep their mappings, removed ones are dropped, new ones become placeholders for you to finish. The pipe drops to review; finish the new columns and re-lock. The file you rebuild from is access-checked in its own right, so a pipe can only ever be re-pointed at a file whose asset manager you hold. |
| It's a genuinely new file type | Build new pipe — jumps to Upload with the held file pre-loaded (no re-upload). Build and lock as in 5.2; the file then executes out of quarantine. |
| The file is wrong or out of scope | Reject — the record is kept for audit; the file stops appearing. |
Not sure whose file it is? Identify client asks Nexus (the identification agent) to read the held file's name, headers and one sample row and suggest which of your configured clients it belongs to, each with a confidence level and reasoning. Suggestions are advisory only and appear as a suggestion card: the best match that corresponds to a configured client leads the card with its confidence meter, the agent's reasoning, and a Build with this client button that jumps to Build new pipe with that client pre-selected (still editable there); weaker matches list under Other options, and a name that matches no configured client shows as informational only. Nexus never assigns data to a client — attribution always happens when you build and lock the pipe.
5.4 Deliver data to a destination (outbound)
Who: Operator (Outbound & Delivery write). Deleting destinations: super-admin.
- Create the destination (Admin mode → Destinations, or just ask Hermes). Give it a name and an adapter — SFTP, FTPS, Email, HTTP POST/API, or a platform-specific adapter — plus connection details. For SFTP/FTPS, credentials travel by secret reference and the host's identity must be pinned via Test connection before the first delivery (see 7.1). A destination can carry multiple targets (channels), so "Morningstar" can have SFTP and email.
- Build the outbound pipe (Outbound Pipes → Build Pipe). Pick the destination, optionally an AM, and upload the destination's template file. An uploaded template is capped at the platform's standard 50 MB upload limit (the same ceiling as inbound uploads) and refused while it is still being received, never read whole first. For binary vendor templates (Bloomberg / Morningstar / BDUP .xlsx), tick "Preserve template format" — Kairo keeps the vendor's workbook byte-for-byte and writes only data rows into it. Without that tick, an Excel template still yields an Excel file: the pipe's output format is XLSX and every run writes a fresh workbook — the header row (plus any preamble rows the template carried) and one row per entity, every cell as text exactly as the mapping rendered it, so a numeric format such as
0.0000or a leading-zero code survives untouched instead of being re-typed by Excel. The egress validation rules run over the workbook's rows just as they do over a CSV. A pipe can also carry an output layout — the sheet's name, the cell where the table starts (A3leaves two blank rows above it), and whether a header row is written at all (some outlets want data from line 1, for CSV as much as for a workbook); the validation rules read the file back through the same layout, and a layout the platform cannot honour is refused when saved, naming the accepted shape. An XML output format writes a document instead of a table: the layout'sxmlenvelope names the root element (with its namespace declarations and attributes, such as anxsi:schemaLocation), any wrapper elements, and the record element written once per entity — each mapping column becomes a child element named by the column, so the column names must be XML element names; the validation rules read the records back through the same envelope. The envelope can also carry header elements — a literal (filename tokens such as{asset_manager_identifier}resolve inside it), the delivered row count, or a timestamp — placed under the root or under one of the wrapper elements, for outlets whose schema wants a sender id, a record count or a submission time before the records. Currency Topics on the way out. A column can ask for the currency of a price rather than its value (the mapping'scomponentrule): it renders the row's price currency — the explicit Price Currency beside the NAV, else the currency in the valuation's key, else the share-class currency cited as an assumption — so a dual-currency class delivers one row per price currency, each labelled with its own. A pipe-level quotation rule (GBP → GBX ×100) delivers minor units to an outlet that quotes in them: for every row in a listed currency the price amounts are multiplied before any number format applies and the currency column reads the quoted code; the share-class currency is never changed, and the rule is refused when saved if it names a currency or factor the platform cannot apply. A pipe can also carry row exclusions — conditions in the same vocabulary as a conditional mapping (a field is empty, the price currency is not USD); a row matching any of them is left out of the file, and the pipe page states them in words. A mapping can concatenate fields and literals into one value (the legal fund name, a space, the share-class extension); when every field part is blank the mapping's default is written instead of a value made only of separators. If the same template family was built before, the mapping clones instantly from a cached profile. - Build the outbound pipe (Outbound Pipes → Build Pipe). Pick the destination and upload the destination's template file. A pipe belongs to an Asset Manager: the working Asset Manager in the picker becomes its owner (the same for bundle and document pipes) — with no Asset Manager selected the build is refused, and only a platform administrator can create an unowned template that every tenant's subscriptions may share. For binary vendor templates (Bloomberg / Morningstar / BDUP .xlsx), tick "Preserve template format" — Kairo keeps the vendor's workbook byte-for-byte and writes only data rows into it. Without that tick, an Excel template still yields an Excel file: the pipe's output format is XLSX and every run writes a fresh workbook — the header row (plus any preamble rows the template carried) and one row per entity, every cell as text exactly as the mapping rendered it, so a numeric format such as
0.0000or a leading-zero code survives untouched instead of being re-typed by Excel. The egress validation rules run over the workbook's rows just as they do over a CSV. A pipe can also carry an output layout — the sheet's name, the cell where the table starts (A3leaves two blank rows above it), and whether a header row is written at all (some outlets want data from line 1, for CSV as much as for a workbook); the validation rules read the file back through the same layout, and a layout the platform cannot honour is refused when saved, naming the accepted shape. An XML output format writes a document instead of a table: the layout'sxmlenvelope names the root element (with its namespace declarations and attributes, such as anxsi:schemaLocation), any wrapper elements, and the record element written once per entity — each mapping column becomes a child element named by the column, so the column names must be XML element names; the validation rules read the records back through the same envelope. The envelope can also carry header elements — a literal (filename tokens such as{asset_manager_identifier}resolve inside it), the delivered row count, or a timestamp — placed under the root or under one of the wrapper elements, for outlets whose schema wants a sender id, a record count or a submission time before the records. Currency Topics on the way out. A column can ask for the currency of a price rather than its value (the mapping'scomponentrule): it renders the row's price currency — the explicit Price Currency beside the NAV, else the currency in the valuation's key, else the share-class currency cited as an assumption — so a dual-currency class delivers one row per price currency, each labelled with its own. A pipe-level quotation rule (GBP → GBX ×100) delivers minor units to an outlet that quotes in them: for every row in a listed currency the price amounts are multiplied before any number format applies and the currency column reads the quoted code; the share-class currency is never changed, and the rule is refused when saved if it names a currency or factor the platform cannot apply. A pipe can also carry row exclusions — conditions in the same vocabulary as a conditional mapping (a field is empty, the price currency is not USD); a row matching any of them is left out of the file, and the pipe page states them in words. A mapping can concatenate fields and literals into one value (the legal fund name, a space, the share-class extension); when every field part is blank the mapping's default is written instead of a value made only of separators. If the same template family was built before, the mapping clones instantly from a cached profile. - Create the destination (Admin mode → Destinations, or just ask Hermes). Give it a name and an adapter — SFTP, FTPS, Email, HTTP POST/API, or a platform-specific adapter — plus connection details. For SFTP/FTPS, credentials travel by secret reference and the host's identity must be pinned via Test connection before the first delivery (see 7.1). A destination can carry multiple targets (channels), so "Morningstar" can have SFTP and email. A destination carries no scope of its own: the older destination-level routing rules (prompted, manual or ISIN-upload) were removed on 10 September 2026 — what a destination receives is decided entirely by the subscriptions on it, and a destination's listing shows the Asset Managers subscribed to it. A run whose subscription resolves no entities sends nothing; it never falls through to a destination-wide rule.
- Create the destination (Admin mode → Destinations, or just ask Hermes). Give it a name and an adapter — SFTP, FTPS, Email, HTTP POST/API, or a platform-specific adapter — plus connection details. For SFTP/FTPS, credentials travel by secret reference and the host's identity must be pinned via Test connection before the first delivery (see 7.1). An SFTP upload is confirmed against the server's own record of the landed file: the receipt carries the size the server reports, and a file that did not land whole is a failed delivery naming both sizes — never "delivered" on the local byte count. A destination can carry multiple targets (channels), so "Morningstar" can have SFTP and email.
- Build the outbound pipe (Outbound Pipes → Build Pipe). Pick the destination, optionally an AM, and upload the destination's template file. For binary vendor templates (Bloomberg / Morningstar / BDUP .xlsx), tick "Preserve template format" — Kairo keeps the vendor's workbook byte-for-byte and writes only data rows into it. Without that tick, an Excel template still yields an Excel file: the pipe's output format is XLSX and every run writes a fresh workbook — the header row (plus any preamble rows the template carried) and one row per entity, every cell as text exactly as the mapping rendered it, so a numeric format such as
0.0000or a leading-zero code survives untouched instead of being re-typed by Excel. The egress validation rules run over the workbook's rows just as they do over a CSV. A pipe can also carry an output layout — the sheet's name, the cell where the table starts (A3leaves two blank rows above it), and whether a header row is written at all (some outlets want data from line 1, for CSV as much as for a workbook); the validation rules read the file back through the same layout, and a layout the platform cannot honour is refused when saved, naming the accepted shape. An XML output format writes a document instead of a table: the layout'sxmlenvelope names the root element (with its namespace declarations and attributes, such as anxsi:schemaLocation), any wrapper elements, and the record element written once per entity — each mapping column becomes a child element named by the column, so the column names must be XML element names; the validation rules read the records back through the same envelope. The envelope can also carry header elements — a literal (filename tokens such as{asset_manager_identifier}resolve inside it), the delivered row count, or a timestamp — placed under the root or under one of the wrapper elements, for outlets whose schema wants a sender id, a record count or a submission time before the records. Currency Topics on the way out. A column can ask for the currency of a price rather than its value (the mapping'scomponentrule): it renders the row's price currency — the explicit Price Currency beside the NAV, else the currency in the valuation's key, else the share-class currency cited as an assumption — so a dual-currency class delivers one row per price currency, each labelled with its own. A pipe-level quotation rule (GBP → GBX ×100) delivers minor units to an outlet that quotes in them: for every row in a listed currency the price amounts are multiplied before any number format applies and the currency column reads the quoted code; the share-class currency is never changed, and the rule is refused when saved if it names a currency or factor the platform cannot apply. A pipe can also carry row exclusions — conditions in the same vocabulary as a conditional mapping (a field is empty, the price currency is not USD); a row matching any of them is left out of the file, and the pipe page states them in words. A mapping can concatenate fields and literals into one value (the legal fund name, a space, the share-class extension); when every field part is blank the mapping's default is written instead of a value made only of separators. If the same template family was built before, the mapping clones instantly from a cached profile. - Review the mappings. Hermes maps golden-record fields onto the destination's columns. The mapping table shows a row for every template column: one Hermes could not map arrives flagged Unmapped — needs manual mapping or default value rather than disappearing — give it a mapping or a default — so no destination column is ever silently out of reach. Review as on inbound: confidence bars, click-to-apply alternatives, inline Default values, Skip toggles — or chat: "map column 3 to ISIN", "set default Currency to EUR".
- Review the mappings. Hermes maps golden-record fields onto the destination's columns. A mapping's normalisation can also shape a number the way the outlet wants it: a digit pattern such as
0.00(fixed decimals),0.####(up to four, trailing zeros dropped) or0.0000####(at least four, up to eight), a standard specifier (F4fixed,N2grouped thousands,Gshortest form), a three-section pattern with literal prefixes for positive, negative and zero values (C+0.00;C-0.00;C0.00— the negative section formats the absolute value, its own literal carries the sign), and a locale for comma decimals (de-DE). A percentage normalisation formats the stored number as it is — the platform stores whatever number the file carried after stripping%, so no times-100 is ever implied — set an explicit multiplier only when the stored value is a fraction. Beyond numbers, a normalisation can slice a value (the first two characters of an ISIN), replace a literal (CNH→CNY), truncate decimals without rounding, render a date as the Excel serial day number some workbook outlets expect, and a chain applies several of these in order — format the number, then prefix it; slice, then bracket — so the wire value is built the way the outlet describes it. A pattern the platform cannot render is refused when you (or Hermes) save it, naming what is accepted, rather than persisted as a rule that looks right and changes nothing. The mapping table shows a row for every template column: one Hermes could not map arrives flagged Unmapped — needs manual mapping or default value rather than disappearing — give it a mapping or a default — so no destination column is ever silently out of reach. Review as on inbound: confidence bars, click-to-apply alternatives, inline Default values, Skip toggles — or chat: "map column 3 to ISIN", "set default Currency to EUR". - Check the validation rules. The egress validation panel shows the mandatory/conditional/value-check rules for the format (e.g. the full Findatex EET rule set). These run before every send, so malformed output is blocked rather than delivered. Every rule in that list is a rule that runs: a rule whose kind the validator cannot evaluate is refused when you (or Hermes) try to save it, naming the kinds that exist, rather than being stored and displayed as though it were enforcing. If Hermes proposes one, the turn says "Validation rules NOT changed" and nothing is written — the rest of its reply still stands.
- Lock the pipe.
- Set scope and schedule via a Subscription (Admin mode → Subscriptions). A subscription = one AM × one destination × one pipe, with: scope mode (Full = everything / Delta = changes since last send), trigger (cron schedule, on new data — fires automatically when fresh golden data lands — or both), and a "Which entities?" choice: everything in the AM's book, matching filters, or an explicit ISIN list. Two rules govern resolution: filters AND each other (an entity must match every filter; for any-of, use the "in list" operator on one field), and an ISIN list is an override, not a filter — when a subscription stores both, the filters are never evaluated (the screen says so, and saving never deletes them). A live "Resolves to N share classes" readout shows exactly what a send would resolve for the current scope before you save. A filter's operator must be one the engine can actually evaluate (equals, not equals, in list, not in list, contains, starts with); anything else is refused when you save it and, if a scope written before this rule still carries one, the send refuses rather than running. That refusal matters more than it looks: filters combine with AND, so a filter the engine quietly skipped would return more entities than you asked for, not fewer. And a delivery that resolves no scope at all sends nothing — an older pipe→destination link carrying no ISIN list, no filters and no client name is refused with the reason logged, rather than falling through to every entity on the platform; no link can ever resolve outside its pipe's asset manager. Absence of configuration is never maximum scope. Type your intent in plain English and let the Scope Agent draft the filters ("all active EUR UCITS share classes domiciled in LU"), preview, and apply. Note: only locked pipes can be subscribed.
- Execute — via the subscription's Execute button, its schedule, or the pipe's Generate action for a full-scope run.
- Check the results. Run History on the pipe shows each run's validation findings and a "N not ready" badge listing any entities withheld by the delivery-readiness gate (expand to see each blocking reason). The Delivered page lists every generated file with download, re-generate, and error details. A failed push raises a
delivery_failedalert in the bell. Generated outputs are always downloadable — stored in S3 in production and on a persistent local volume on a dev stack. Output rows anchor on the grain the template's own field levels declare (an EPT's share-class fields → one row per share class; a NAV feed still anchors on the valuation rows beneath them) — a stray value on an unrelated entity type can't change the output's shape — mapped identifier columns (ISIN) fill from the entity's own key when no stored field exists, and an output whose rows are nearly all empty raises a loud "OUTPUT NEARLY EMPTY" finding instead of reading as a clean run. And every Findatex template (EET, EMT, EPT, TPT) now has a full-spec egress rule set: a pipe with per-pipe rules validates against those (Hermes-editable), and a pipe with only a recognisedformat_templatefalls back to the built-in full baseline instead of reporting "skipped".
5.5 Daily data-quality operations
Who: Analyst or above (quality write).
A practical daily loop:
- Start at the bell (or Pulse). Work alerts top-down; each deep-links to its item.
- The Publication Board (
Publications). The forward view: is every window on track? An at-risk row means the data a publication needs isn't held yet at the date it needs — before anything has failed. The alert's second line already answers "chase the source, or investigate the pipe?"; the row shows Held M of N — counted in share classes (valuation sub-entities never inflate the number), with a field mapped from a higher level (a fund name or domicile) satisfied by the share class's own fund — and the countdown to cut-off. Nothing failing here is the goal, and the check runs continuously (every half hour, plus the moment new data lands — alerts auto-resolve when the gap fills). - Data Quality (
Quality → Data Quality). Review discrepancy counts by severity and status. For a deeper look, pick an AM and Generate Report — an AI triage report clustering the issues, suggesting root causes and actions. Reports are tenant-scoped at both ends: you can only commission one over an asset manager you hold, and you can only open a finished one over an asset manager you hold — a report's link is not a key to it. With a single-AM login the report defaults to your own book without asking. A platform-wide report (every asset manager at once) can be commissioned and read by administrators only. Check the Overdue NAVs (and other completeness) panels: entities whose expected data hasn't arrived — decide "real gap" vs "legitimately stale" (holiday schedule, paused AM). Check the document completeness panels above them — a share class without a current KID is a compliance gap, not a data gap: register the document (see 5.7) and the row clears on the next load. - Lookup Review. Every pending item is an unknown source value with an AI-proposed canonical. For each: Confirm the proposal, Override it (enum targets give you a dropdown of the only valid values — you cannot type an invalid one), or Reject (the cell stays empty on future runs). Pick the scope — pipeline or AM (global is offered to platform admins only, because a global entry rewrites every Asset Manager's values) — to control how widely the mapping applies. Everything you decide here is remembered: future runs resolve automatically. When the autonomy policy is set to act-with-notice, high-confidence proposals inside the blast-radius ceiling clear themselves after their hold window — through the same promotion path your Confirm uses — leaving your queue holding only the items a person should actually look at.
- First Production Check. New share classes wait here before delivery. Click "inspect in Explorer →", sanity-check the golden record, then Approve (releases it) or Reject with a comment (holds it until fixed). Already-live share classes are never retro-held.
- Structural Conflicts. The platform's rule is absence is not conflict; contradiction is. A record that tries to move an entity to a different parent, or change a stable identifier (ISIN, SEDOL, product code), is held here showing current → proposed. Approve applies the change; Reject keeps the existing structure. Ordinary value updates (NAV, fees) never appear here — they flow through golden-record policies as normal.
- PPC diffs — if you run destination reconciliation, sweep
/ppc(5.6).
5.6 PPC reconciliation (checking a destination's copy)
Who: Analyst or above; marking a source as PPC requires admin access.
PPC answers: does the destination's copy match what we sent them? The one rule to internalise: mismatches are chased with the destination, never the Asset Manager — and a read-back must never feed the golden record (the platform enforces this once the source is flagged).
Reconciliation covers every data kind the destination echoes back, and the screen partitions them with the data-type pills — All data / Static / NAV / Dividends (by Openfunds field family: static reference data, valuation dynamics, the dividend series). The pills drive the diff list, the stat cards, the breakdown charts and the accuracy panel together, so "how accurate is this vendor on static data?" is one click, not a query.
- Ingest the read-back as a normal inbound pipe. The destination's copy arrives as a file; build and execute it like any other (5.2).
- Mark it as PPC. On the pipe's detail page use Mark as PPC, or on Policies use the PPC toggle in the Data Sources table — and pick which destination's copy it is. Flagging excludes it from golden-record computation.
- Re-evaluate (Policies → Evaluate) so golden is recomputed without the read-back.
- Compute diffs on PPC Reconciliation (an AM must be selected).
- Review. The page shows accuracy against the 97–99% target, per-destination "who to chase" bars, and the diff table. Diff types: Value changed (we sent A, they hold B), Missing at destination (a coverage gap), Extra at destination (they hold something we didn't send).
- Resolve each diff: Accept Golden (our value is right), Accept PPC (theirs is right), or Manual Override (needs investigation). Recomputing is safe — resolved diffs are kept for audit; unresolved ones refresh.
5.7 Manage fund documents
Who: Operator for uploads/edits; visibility and permalink actions deserve care.
Documents (prospectuses, KIDs, factsheets, reports, SFDR) arrive five ways — manual upload, email/SFTP push, a manifest drop (a metadata file plus the documents it names, as one arrival), fetched from links in a data feed, or scraped from AM websites — and all land in the one document store on the Documents page.
Everyday tasks:
- Find a document: filter by type / language / country / visibility / AM, or search — the search runs on the platform over the filename and the linked entity keys, so an ISIN finds every document that covers it (its own, the ones that represent it, the ones that cover it). "Latest only" (default on) hides superseded versions. Or ask Argus: "List KID documents expiring this quarter."
- Upload with metadata: Upload document → pick the AM and file. If the filename matches the AM's intake conventions it registers automatically; otherwise complete the short metadata form (type, entity, languages, countries, date, visibility). New documents default to restricted.
- Set intake conventions (per AM): define filename patterns like
{identifier}_{type}_{languages}_{countries}_{date}.pdfso future pushes self-register. Files matching no pattern are held for metadata (bell alert) — never guessed. Two more sources live here. Embedded properties → field: a publisher's PDF carries DocInfo (Title, Subject, Keywords, dates) and a Word file its core properties; map the ones that mean something for this AM (Keywords = identifier,Subject = type,dc:language = languages) and they fill what a higher source left empty — with no map they are read but never applied. Document extensions: a file that arrives without metadata is treated as a document only if its extension is in the AM's list (empty =.pdf .doc .docx) — an AM that files factsheets as.pptxadds it; anything can still be registered as a document deliberately. Level pins per type (PRP = fund) say where a type attaches for this AM. Every registered document records where each field came from — manifest column, embedded property, filename convention — shown as Metadata sources in its drawer. You can also set a default visibility for self-registered pushes; choosing public — which makes every matching document readable without a login — requires you to tick the explicit confirmation (a platform administrator can set it without the tick), and the change is recorded. - Manifest drops — a metadata file plus the documents it names: when a counterparty sends a spreadsheet whose rows list the files arriving with it (an email with
manifest.csvand forty PDFs; a zip; an upload), create a Manifest pipe (Documents → Manifest pipes): pick the AM, paste the manifest's column headers exactly as the file spells them — they are the pipe's signature — and say which column names the file, the type (or one fixed type), the identifier, languages, countries and date. Lock it. From then on every drop whose manifest carries exactly those columns registers automatically: each row's file goes through the same registrar as any document (type level, lineage, supersession), a file named by several rows becomes one document covering all of them, and the row's values beat the file's embedded properties and its filename. A row naming a file that is not in the drop registers nothing and is reported (companion file not found); a document no row names is held (not named by manifest); a row carrying only a URL is reported (a manifest never fetches — links belong to a data pipe's document fetch). The manifest itself lands on the Ingested page as ingested when every row registered, else held for metadata with the drop's summary and one bell. A manifest with an added column matches no pipe and quarantines like any unrecognised data file — a new column is a mapping decision. Drops arrive by email (the attachments of one email are one drop), by Upload manifest drop (pick the manifest and its files together), from a zip (extract the manifest with its companions), and over a transfer source (the manifest alone — its rows are reported until the companions are grouped, a named follow-up). - Edit metadata / links: open a document's drawer to edit its type, languages, countries, dates, and visibility, and to manage which entities it covers (one KID can represent sibling share classes).
- Where a document attaches: every document type has a level — a Prospectus is an umbrella fact, an Annual Report a fund fact, a KID a share-class fact (Company-level documents attach at the umbrella). When a document arrives keyed by a share class (an ISIN in a filename or a metadata-file row) and its type sits higher, the platform walks the entity hierarchy and attaches it at the type's level: the umbrella or fund becomes the document's subject, and the share classes named stay on it as covered links, so the same prospectus arriving for different share classes is one document lineage, not several. If the hierarchy cannot be walked (an orphan share class with no parent loaded) the document registers at the level it has, with a level unresolved note and a bell — nothing is guessed. The link picker in the drawer offers subject / representative / covered and accepts instrument levels only. Per Asset Manager, Intake conventions can pin a type to a different level (for example KIDs at fund level) via the level overrides. Documents registered before this rule can be re-attributed: a platform administrator runs the level backfill, which lists every proposed change for review — a person confirms or rejects each one; nothing is re-linked silently. A review item only ever acts on a document belonging to the same Asset Manager as the item itself: if the two disagree, confirm and reject both refuse and ask for the backfill to be re-run, rather than re-attributing across a client boundary.
- Documents by entity: the API
GET /entities/{key}/documentsreturns everything that applies to one instrument — its own documents, the ones a sibling represents it with, the ones that cover it, and the ones it inherits from its fund and umbrella (a share class inherits the umbrella's prospectus; an umbrella never lists its share classes' KIDs). The Fund Explorer shows exactly this in a Documents card on every entity (see 6.2), and Open in Documents from that card opens this page filtered to the entity (an entity chip in the filter bar; × clears it). - Versions & supersession: re-uploading the same logical document with new content automatically becomes version N+1 (the old version is archived). Ambiguous overlaps land in the Supersession review panel: Merge as new version (it joins the lineage; the permalink follows it) or Keep separate.
- Permalinks: each lineage has a stable URL that always serves the latest public version. Restricted documents are never reachable by permalink. Rotate token kills the old URL instantly (use if a link leaked). Every serve is logged.
- Validity: the Validity panel and daily alerts surface documents expired or expiring within 30 days (e.g. KIDs approaching 12 months). Confirm or correct validity dates in the drawer.
- Review an AI extraction: when a PDF is extracted, the Document Review page shows every extracted field with confidence and a citation back to the source page. Fix low-confidence fields (ask the Doc Review Agent to re-check a page), then Approve & Write to commit the values — or Reject. Approve writes only the fields the template declares: if the AI (or a chat correction) surfaces an Openfunds field the template never asked for, it is refused and reported rather than written — a document can never define what fund data it becomes. What is written lands durably and refreshes the golden record for the entity, so it appears in the Explorer and Golden Record pages straight away; if the write cannot be committed, the extraction stays in review and the error is shown — an approval never reports values it did not land. Where the values go (WS-F): an approval writes against the document's own entity links — the share class, fund or umbrella the document is attached to (a KID linked to two share classes writes both) — never against whatever ISIN the model happened to read; when the model's key disagrees with the links, the review page says so as a finding and the links still decide. A document with no entity links is the one case the model's key is used, and only after you tick Confirm entity … — until then Approve stays disabled. Every value written this way carries the document as its provenance (which document, which extraction, how the entity was chosen), so the golden record can rank document-derived values against feed values: each asset manager's documents of a type appear as their own source in the Policies → Sources registry (ranked below the feeds by default — a prospectus never outranks a feed by accident; an administrator raises it deliberately), and a document value that disagrees with a feed value is one discrepancy whose source names the document. Auto-extraction on registration: bind a document type to a template — the platform default per type through the document-types API, or per asset manager under Documents → Intake conventions → Auto-extract on registration (none switches it off for that manager) — and every newly registered document of that type is extracted at once, landing in review; approval is always a person's act. You can also start an extraction from any document's drawer (AI extraction → pick a template → Extract) — the extraction is created from the canonical document itself, so the linkage is never guessed.
- Deliver documents: sending a document is a delivery like any other. Create a Document Pipe (Outbound Pipes → Document Pipe: name, destination, and what leaves — the documents themselves as raw files, or a metadata file with a permalink for each public document), lock it, and give each Asset Manager a subscription on it (Admin → Subscriptions). The subscription's document rule says which documents: types, languages, the countries a document must be valid for, and optionally a destination country — with one, a document is sent only where every entity it covers has a country registration on record (no evidence = withheld and flagged, never silently sent). The entity half of the scope (filters / ISIN list) narrows to the documents covering those entities; leave it empty for every document of the AM. The readout under the rule says N documents would send · M withheld with the reasons, before anything goes. At send time every document passes the gates in order — country registration, validity (an expired document is withheld with expired since …; an expiring one sends with a warning; ticking allow expired — write access — lets it through, flagged on the run), the CCI identity check, and first production check when the pipe enforces it. Full vs delta works as for data: delta sends only document versions this destination has not already received. Every send is a run on the pipe with one receipt per document version (or one per metadata file, naming the versions it listed), on the Publication Board and in the close-of-day ledger; a run that sends nothing says why (skipped — all withheld, all already delivered, nothing in scope). Hermes can preview a document subscription and send it — a send from chat always shows the preview first and asks you to confirm. Subscriptions set to On new data fire when a new document version registers for the AM. The older doc-delivery API remains for one release but writes no receipts; use a pipe and a subscription.
5.8 Golden record, time travel, and undoing an ingest
Who: Viewer and above (read); revert/restore requires pipe access.
- Look up an entity's truth: on Golden Record, ask Chronos ("Show the golden record for LU0234567890"). Every field shows its winning value, source, confidence (green = sources agree), effective date, and freshness (green "Confirmed 2h ago" → red "Stale"). Low-confidence or stale fields are your review targets.
- See why a value changed: Policy Stream (Admin mode → Configuration) shows the change history for an entity — old value → new value, which source won, and why ("new event" = fresher data arrived; "source priority change" = a higher-ranked source differed).
- Ask point-in-time questions: on Time Travel, fix any of the three axes: "What did we know about IE00B4L5Y983 last Tuesday?" (receipt time — this is how you reproduce a historical state for an audit), "Show NAV history from all sources" (source axis), or combine axes.
- Undo a bad ingest: every ingest is tied to a run. In the pipe's Run History, click Revert on the offending run — its data instantly stops counting toward the golden record (previous values win again), while full history is preserved. Restore brings it back. Both actions are audited. This is the standard remedy when a mis-mapped file has polluted an entity: revert, fix the mapping, re-execute — and that loop now works for transaction-cost stores too: a reverted run's trades and ADL figures no longer block re-ingesting the same file (duplicate checks scope to ACTIVE rows), so the corrected values land on re-execution. Once a file has been re-ingested, restoring the old run is refused with the reason (it would duplicate active rows) — revert the newer run first if you genuinely want the old one back.
5.9 Transform rules — mapping logic without code
Who: Operator. Where: pipe detail → click a mapping row → Mapping Inspector → Edit → Build transform rule.
Most mappings are simple: one source column feeds one Openfunds field, with the platform handling formats (dates, numbers, enums) automatically. A transform rule is for the cases where the logic is richer — the value depends on another column, lives in several columns, or one column actually drives several fields. The rule builder is a form (no code, no JSON); every rule can be previewed against your real file rows before saving, and an invalid rule (for example one referencing a column that doesn't exist) is rejected with the reason on the spot.
Derived columns — a 21st column from a 20-column file. When the field you need isn't any single column but a combination of several (a concatenation, a calculation, a conditional), use + Add derived column at the top of the Column Mappings table. Give it a label, pick the target field, and build its rule — only the rule shapes that need no source column are offered (concatenate, computed, conditional), and the rule is required at creation, because a derived column's value comes entirely from its rule. The new row appears in the mapping table marked derived (ƒ, no source samples — there is no source), previews and executes like any other mapping, and can be deleted from its Mapping Inspector (a real delete: nothing in the file backs it, so there is nothing to skip). Two mappings can never share the same target field — creation refuses with the clash named. Derived columns cannot reference other derived columns.
One column, several fields. One column often carries several facts — a full share class name like Aldgate Global Equity - Class I (USD) Acc should populate Full Share Class Name and Share Class Currency. Open the column's Mapping Inspector and use + Map to another field: pick the second target and, when only part of the value is wanted, tick Extract part of the value and give a regular expression with a capture group (e.g. \(([A-Z]{3})\) pulls the currency out of the brackets). Preview extraction runs the pattern against your real file rows through the platform's actual normaliser — what you see is exactly what a run would produce — before anything is saved. The new mapping appears in the table grouped under its column (marked ↳ — same source, another target), each sibling with its own normalisation, and locking/executing writes all of them. Two siblings can't share the same target: creation is refused with the clash named, rather than one of them being silently dropped later.
There are five rule shapes. Choosing the right one:
1. Conditional — "the value depends on another column."
Pick a discriminator column, then build one or more cases: if column X equals/contains/starts-with some value → take the value from column A (or use a fixed text); otherwise → column B / fixed text / nothing. Cases are evaluated in order and the first match wins, so you can stack several.
Example: a ratings file where the usable value depends on the agency — if owner_code = "MSTAR" take rating_value, if "FITCH" take rating_name. Or a simple default: if share_class_type is empty → fixed text "Retail".
When the target field is an enum, a fixed value that isn't in the field's accepted values gets an amber "not in accepted values" warning before you save.
2. Fallback chain — "use the first column that has a value."
An ordered list of source columns; for each row the first non-empty one wins.
Example: take NAV (EUR); if blank, fall back to NAV; if that's blank too, leave the field empty. Use it whenever a feed splits the same fact across alternative columns.
3. Concatenate — "join several columns into one value."
Ordered columns plus a separator.
Example: first_name + last_name → Portfolio Manager Name, separated by a space.
4. Computed — "simple arithmetic on numeric columns."
An expression over column references, inserted via col[N] chips.
Example: a fee delivered in basis points → col[7] / 100 to store the percentage. Expressions are restricted to plain arithmetic for safety — anything else is rejected at save.
5. Boolean dispatch — "one column drives several yes/no fields."
A table mapping each value of a source column to a different target field.
Example: a Product column whose values are "UCITS", "AIF", "ETF" … — each value sets its own Openfunds flag to yes. Two switches: set others false (rows not matching a value get no written to the other flags in the table, instead of nothing) and case-insensitive matching. Saving a dispatch rule clears the mapping's single target — the dispatch table now owns the targets.
Good to know:
- Atlas can write these too. Anything you can build in the form you can also ask for in the pipe chat ("if the owner code is MSTAR, take the rating from column 6"). Chat-proposed rules pass exactly the same validation — an invalid one is refused, never silently saved.
- The builder is the blue path; Atlas is the violet path. Same result — use whichever is faster for you.
- Other transformations you'll see in the Inspector's Transformation panel are attached automatically rather than authored here: lookups (value translation with HITL review), date/number format handling, settlement-period parsing ("T+2" → 2), and currency qualifier on NAV/AuM column pairs (routes each column's value to the right per-currency record). You rarely touch these by hand — the Inspector describes what's active in plain English, and the preview shows the effect.
Row exclusions — agreed removals as declared config. Some feeds carry rows an agreed business rule says must never load ("funds starting with Loans are removed"; "ADL entries for these funds are removed"). The Row exclusions card on the pipe detail page declares those rules on the pipe itself: each rule has a name (the attribution you'll see on every run) and a condition over one or more source columns — the same operators as conditional mapping rules (equals, contains, starts with, is one of, is empty, …, combined with AND/OR). Rules are evaluated per row before mapping, on every ingest path including transaction and ADL feeds and the bulk loaders. Nothing is silently dropped: every run counts its exclusions per rule (rows excluded on the run row, a finding naming each rule and its count), the dry-run preview shows exactly which rows a rule would take, and the run arithmetic still sums — succeeded + failed + excluded + never-attempted = input rows. Rules are validated when you save them AND re-checked at run start; a malformed rule fails the run loudly rather than half-applying an agreed removal. To load excluded rows again, edit or remove the rule and re-execute — each run's counts show which rules were in force when it ran.
Excluded sheets — declaring that a workbook sheet is not data. Excel workbooks routinely carry sheets that must never be read — a cover sheet, a notes tab, pivot scratch, a glossary. The Excluded sheets card (workbook-format pipes only) declares them on the pipe by exact name or glob pattern (Notes*, Sheet? — matched case-insensitively), each with an optional note; who declared each pattern and when is stamped automatically. The effect is platform-wide and never silent: the sheet matcher stops prompting "build a pipe" for excluded sheets and labels them "Excluded by pipe config" (a genuinely unknown sheet still prompts — exclusion never hides new data); every run of the pipe names, in its findings, exactly which sheets its patterns excluded and by which rule; and a run whose own source sheet matches an exclusion pattern refuses to run rather than read an excluded sheet — fix the pipe's sheet or the pattern. Patterns are checked against the workbook's actual sheet names when you save: a pattern matching nothing is a warning, not an error (the next delivery may carry the sheet). If the set of excluded sheets present in a delivery changes between runs — an excluded sheet appears or disappears — the platform raises a drift alert (the same surface as schema drift), because a change in what you're deliberately not reading is signal, not noise. This is name-based and lives on the pipe; the per-signature Ignore action on unmatched sheets (section 5.2) remains for one-off sheets you simply don't want to be asked about.
Bounded data blocks — several tables in one source. A file sometimes carries more than one table: stacked (a NAV block, a blank row, a fee block below) or side by side (columns A–F one table, H–M another) — and this is not an Excel-only shape; CSVs do it too. Each table gets its own pipe reading only its block. On upload, Detect tables proposes the blocks it can see (split by blank rows, a second header row, or blank column separators) and Build pipe from this table builds against exactly that block's headers and rows. The pipe's Row layout card shows the bounds — header row, last data row, first/last column, all in 0-based source coordinates, beside the data-start / ignored / evidence rows of 5.2; all empty means the whole source, exactly as before. Each block keeps its own signature (two side-by-side tables with identical headers never collide), its own mappings, drift and run history. Guard rails: blocks on the same source may not overlap; the column width is fixed once mappings exist (delete the pipe and re-declare the block from the file to change it — moving the row end is always allowed, that's the routine fix when a table grows); and a moved boundary is never silent — data continuing past the declared end, or a blank tail inside the block, raises a drift alert and a run finding naming the rows.
Multi-manager files — the data decides the tenant. An administrator or service provider often sends one file carrying many managers' data. With Split multi-manager files enabled on the pipe (Tenancy card — off by default), executing a raw arrival first resolves each row's manager from the data itself: the row's identifier (ISIN, SEDOL, fund code, umbrella key — whatever the row is about) is looked up in the identifier registry, and the manager who already owns that entity owns the row. Where the file genuinely carries a manager code, an optional AM-code column can be declared instead. The file then fans into one child file per manager — each executing as a completely normal single-manager run with its own counts, findings, history and revert — plus a quarantine child holding every row that resolved to no manager (or to more than one: an identifier claimed by two managers attributes to neither, naming both). Nothing ever falls back to a default manager or to the sending channel's manager. The parent file records the whole split — per-manager row counts, refused managers (a child whose manager you can't access is refused and reported, never silently skipped), unresolved counts — and the arithmetic always sums. Unresolved identifiers appear on Quality → Tenant Review: assign each one once (the answer persists — that identifier resolves automatically forever after) or reject it, then replay the quarantined rows to attribute them. The split reads the parent file under your own access: you can only split an arrival whose manager you can reach, and the quarantine child belongs to that same manager — its unattributed rows are never visible outside the arrival's own tenancy.
Attach-only — structure comes from reference pipes. A pipe whose payload is not structural reference data (NAV, dividend, holdings, E*T families…) may attach facts to entities that already exist; it may not mint an Umbrella, Fund or Share Class — encountering an unknown share class is a gap in onboarding, and the run says so, naming the missing entity. Dated series still create their own per-date entries (today's NAV point is new by construction); it's the parent they may not create. This is now the default: static reference pipes (share class / fund / umbrella) may mint structure, everything else attaches. The Tenancy card's Structure minting control overrides it per pipe, explicitly, in either direction.
Cross-level operations — deliver at one grain, store at another. Data routinely arrives at a different level from the one it belongs at: a fund-level file carries values that belong on every share class; share-class rows compose a fund-level figure. A mapping can declare a level operation (Mapping Inspector → Level operation) — five distinct choices, never conflated and never defaulted: Broadcast copies a parent value down to every child (management company, benchmark); Aggregate composes one parent figure from the children (sum or weighted average) and refuses an incomplete child set (summing 8 of 10 classes is a confident wrong answer) or mixed currencies; Allocate divides a parent quantity down by a declared basis (e.g. fund AUM by shares outstanding) — a missing basis for any child refuses the whole fund naming the child (never an equal split, never a partial allocation), and the children sum exactly to the parent under the largest-remainder rule; Designate takes the parent value from one nominated child (a fund's currency from its reference class) — no arithmetic, and adding another class never silently changes it; Reconcile checks that the file's two grains agree (components vs stated total) and writes nothing — disagreement raises a finding, agreement is recorded as a passing check. Every derived value is marked derived (operation, basis, source entity ride the event), so a later observed value wins under the normal golden-record policy; the Openfunds level stays authoritative for placement. Before you can save an operation you must Preview the fan-out — one source row expanding to N values with the per-child arithmetic visible — and every run reports rows in, values derived, and refusals with reasons. Changing a basis never restates history: values move only when a run re-executes, which is itself audited.
Outbound conditional rules — what a delivery column shows depends on another field. On an outbound pipe a mapping's conditional rule decides between two branches by looking at golden-record fields: one condition (field, operator, value) or several combined with any / all, each of them negatable. Operators: equals, not equals, contains, starts with, starts with any of, is one of, is empty, is not empty (text, case-insensitive), and less / less-or-equal / greater / greater-or-equal (numeric — a non-numeric value never matches). Each branch is a golden-record field, a literal (then_literal / else_literal — a fixed marker like C- when nothing is due), or nothing at all, which delivers the mapping's default value — an empty cell when there is none. That last shape is how "blank this column when …" is expressed: then nothing, else the source. A rule the platform cannot evaluate is refused when saved (by you or by Hermes), naming the accepted operators, rather than kept as a rule that looks right and does nothing.
5.10 Advanced pipe shapes — one row, many records; linked files; shared templates
Who: Operator. All of this lives on the inbound pipe detail page.
Most files are simple: one row = one share class (or one fund). This section covers the four situations where a file's shape is richer, and the pipe-page tools for each.
a) Composite keys — one row per (thing × dimension)
Some data is naturally "per parent, per something": one row per fund × portfolio manager, per share class × country registration, per share class × rating agency. If such rows were keyed on the parent alone, each row would overwrite the last — two managers on one fund would collapse to one.
In the Entity model card ("What does each row represent?"), pick the entity type and tick the key columns in order — e.g. product_code then person_code. Each distinct combination becomes its own record (keys like KFN001:ANGUY, KFN001:MBREN), nested under the parent. Always click Preview before saving: it shows exactly how many distinct records would be created and sample keys, so a wrong column choice is obvious immediately.
The common time-series shapes don't need this — picking the Valuation, Dividend, Holding or Listing entity type builds the right key automatically once you point at the date/currency/exchange columns.
Currency Topics. A share class can publish prices in more than one currency — a NAV struck in EUR and in USD for the same class — so the platform never assumes the share-class currency is the price currency. Each (share class, price currency) pair is a Topic, recorded in a registry that fills itself: a class's own currency registers the moment its first NAV or dividend is written, and a currency that arrives in a file registers too (as confirmed when it is the class's currency, as proposed otherwise). A proposed Topic holds its rows: the run refuses every row in that currency, naming the review, and one item appears on the Structural Conflicts page — "class X has never published in USD" — where a person confirms it (the class now publishes in that currency; the next run writes) or rejects it (permanent until someone lifts it; a file carrying that currency is refused on arrival). The agents never confirm a Topic — it is an entity decision, not a value. Arrival magnitude check. Every NAV and dividend amount is compared with the Topic's last golden value: an arrival within ±5% of 100× or 0.01× is refused as a suspected minor-unit (pence-for-pounds) error, and one more than 10× away either side is refused as a suspected wrong Topic or wrong share class — the run's error log names the last value and its date, the incoming value and the cure (declare the quotation code such as GBX, or check the file's currency and identifier columns). A Topic's first-ever value has nothing to compare against: it is written and stamped first observation, so the second arrival is checked. Both bands are platform settings (PIPELINE_TOPIC_MINOR_UNIT_TOLERANCE_PCT, PIPELINE_TOPIC_WRONG_TOPIC_FACTOR). Existing valuation history seeds the registry on first start. When a row carries no currency the platform uses the class's only confirmed Topic and marks the event assumed; a class with two or more Topics refuses currency-less rows, and a class with no known currency at all refuses until its share-class currency is ingested or the pipe maps a currency column. Every valuation and dividend event carries its price currency (OFDY000010) explicitly, so a delivery can always say what currency a price is in. Minor units are quotations, not currencies. A file that quotes in pence (GBX, GBp), South African cents (ZAc) or agorot (ILA) is stored in the major unit: the currency becomes GBP / ZAR / ILS, every price-currency amount on the row (bid, ask, valuation and transaction NAV, and the per-share tax amounts — dividends included) is divided by the factor, and each event remembers how it was quoted (quotation: factor 100, quoted GBX) so a delivery can quote it back the same way. A GBX in a currency column is never refused as "not a currency" and never queued for review; the platform never infers a quotation from the size of a number — only from the code, and the table of codes is fixed (extendable by configuration, never by data).
b) Decomposition — one row carries several different entities
Rarer, but real: a "static data" file where a single row describes the fund and its share class and a benchmark, often with a discriminator column saying which parts apply per row.
Use the "Split one row into multiple entities" panel: click Propose and Atlas analyses the headers and sample rows, returning a proposal — the entities it sees, each one's type, key column and parent, which mapped columns belong to which entity, and its reasoning. You then Apply as-is, Edit the spec (JSON) first, or Dismiss. Once active you can + Add entity or Clear. Two safeguards: the pipe cannot be locked with an incomplete decomposition (it would silently write nothing), and a row whose discriminator matches no rule writes nothing rather than guessing.
c) Multi-value groups — one cell or column set carries several values
A cell like DE, AT, CH is really three country registrations; a manager-name column paired with a role column is really N manager records. The "Detected sub-entity groups" panel shows what the platform found: clear cases are auto-confirmed, ambiguous ones wait for your confirm/reject. Each confirmed group fans out into proper child records instead of one comma-blob value.
d) Batch sequencing — linked files that must load in order
Some AMs deliver a set of files per date: a master (creates the funds/share classes) plus dependents (managers, registrations, ratings) that reference the master's codes. If a dependent loads first, it points at records that don't exist yet.
Use the "Batch sequencing" panel on each dependent pipe: Propose dependencies scans the AM's other locked pipes for shared key columns and suggests which pipe(s) this one should wait for, plus a batch key pattern (how to read the batch date out of the filename — pre-filled, editable). After applying, a dependent file that arrives early simply holds as pending batch and runs automatically the moment the master's run for the same date completes. No manual ordering.
e) Generic templates vs AM-specific pipes
- An AM-specific pipe (the default) serves one Asset Manager's file family — matched by content and filename pattern.
- A generic template pipe (no AM, "Generic template" chip) serves an industry-standard format — the Findatex EET / EPT / EMT / TPT templates — for every AM with one pipe. It matches on content only, and you pick the AM at execute time (or when ingesting a matched file). Standard Findatex columns map deterministically, without AI.
- The header buttons Make generic template / Bind to AM… convert between the two. A generic template is a platform resource: every Asset Manager can see and run it, so editing one — its mappings, rules, document fetch, lock state, or making a pipe generic in the first place — is reserved for platform administrators; ordinary users can still read it and execute it against their own Asset Manager. Re-tenanting a pipe (changing its Asset Manager) happens only through the Bind to AM… action, which checks your access to both the current and the new Asset Manager and records an audit entry; the plain pipe-settings save refuses ownership changes.
- The AM you pick at execute time owns the run. The pipe is only the shape: the run, its Run History entry and its generated file all belong to the Asset Manager the run was executed for — never to the template — and that is who can see, download and re-generate them. Executing a generic pipe (inbound or outbound, including via a destination link) without declaring an Asset Manager is refused with a message saying exactly that; on the outbound side the same holds through the API's
asset_manager_idparameter, and a subscription's own Asset Manager counts as the declaration.
f) Change management on a live pipe
- Compare Mappings (header button, when a previous lock exists) shows a diff of the current mappings vs the last locked version — added / removed / changed (before → after) — so you can see exactly what a re-review changed before locking again.
- Rebuild re-runs the AI mapping from scratch (confirm required). Your entity model configuration is preserved — a rebuild is a re-map, not a re-classification.
- Unlock is always reversible and never loses run history; while unlocked, arriving files for this pipe will quarantine rather than process, which is the safety working as intended.
5.12 Onboard share classes at a vendor — the new-launch pack
Who: Operator with delivery write access; an administrator configures the pack.
When a new share class launches, a market-data vendor typically wants three things before it will carry the instrument: its Insert form (the vendor's own template), the full NAV history, and the latest Prospectus and/or KID. In Kairo that requirement is a property of the destination, not something an operator assembles by hand each time.
- Configure the pack (administrator, once per destination): Delivery → the destination row → Pack → Configure pack. Tick the components in file order — Insert form (pick the locked pipe that renders the vendor's template), NAV history (csv or xlsx; the NAV field by default, other Openfunds fields if the vendor wants them), Latest documents (the types — e.g.
KID, Prospectus— optionally languages and countries; tick required when a share class with no current document of a listed type must NOT be sent) and/or a Document list (a metadata file with permalinks). Set the history depth (empty = full history; some vendors accept a bounded number of years — 7 has been accepted before). Set the file budget if the vendor's per-file limit is known (rows, bytes, share classes — any combination); leave it empty if it is not, and the historical rule applies: a fixed number of share classes per file (50 by default, the fallback per file). Nothing about the vendor's limit is guessed: the budget is configuration, and the platform fits each instruction to it at run time — as many share classes per file as fit, more than fifty when they do. Optionally tick instruct automatically when a share class's first production check is approved — a daily sweep then instructs approved, not-yet-onboarded share classes without anyone typing an ISIN. Saving creates the destination's own pack pipe (a fuchsia pack chip on Outbound Pipes) whose runs are the instruction's file sets; it has no mappings and cannot be subscribed. - Instruct share classes: from the same Pack panel → New instruction (pick the Asset Manager, paste the share classes), or from the Fund Explorer — select a share class and press Instruct to destination… in its header (pick a destination that carries a pack). Press Preview plan first: it says how many share classes are already onboarded at this destination (each named with the instruction and file set that carried it — they are dropped, never re-sent silently), how many file sets the budget yields and their suffixes (
_01,_02…;_001from a hundred sets), the estimated rows and bytes, and any findings — a share class that alone exceeds the budget goes in its own file set, flagged, never dropped; a share class with no NAV observation in the window is named. Then Start instruction. Nothing is rendered until you do; the list must be previewed again if you change it. - What happens next: the instruction runs in the background, one file set at a time, strictly in order, one instruction at a time per destination. Each file set is one run on the pack pipe: the Insert form rendered through its own pipe for exactly those share classes (the pipe's egress validation applies — a blocked form fails the file set, because a vendor form with an empty mandatory cell is a rejection at the vendor), the NAV history streamed from the golden record (gaps are counted and reported, never filled), the latest documents through the same gates a document pipe applies. Every file carries the set's suffix; every artefact is receipted with planned and actual rows and bytes, so the estimate is checked by every run. A file set is delivered only on observed delivery — the same rule as every other delivery.
- When a file set fails (an SFTP refusal, a blocked Insert form, a missing required document): the instruction stops there as partial; the earlier sets stay delivered and are never re-sent, the later ones stay queued. Fix the cause, then Retry — it resumes at the failed set. Cancel skips the queued sets and leaves the delivered ones as history.
- Where to watch: the Pack panel lists the destination's instructions (status, N/M file sets, share classes, trigger, per-set rows planned → actual, the run each set produced, the reason a set failed) and the Publication Board shows every instruction in flight in its own card — an instruction has file sets, not windows, so it never poses as a subscription row.
Part VI — Screen reference: Platform mode
A page-by-page reference. Each entry lists what you see, what you can do, and the AI panel (if any). All pages follow the working-AM picker unless noted.
5.11 Transaction-cost (TCA) pipes — ingesting portfolio transactions
Who: Operator.
Transaction-cost disclosure (PRIIPs / MiFID) needs every portfolio trade — with its arrival timestamp, execution price and fee breakdown — loaded before any cost calculation can run. TCA pipes ingest those trade files. They differ from normal pipes in one way: each row is an immutable trade, so instead of feeding the golden record, rows land in a dedicated transaction store, de-duplicated on their Trade ID — re-sending the same file (or an overlapping quarter) can never double-count.
How to build one. On the Upload page, tick Transaction-cost (TCA) pipe before clicking Build. The mapper then maps columns to the TC transaction vocabulary (Trade ID, Portfolio Code, Transaction Type, timestamps, quantities, the fee breakdown, instrument identifiers) instead of Openfunds. Files shaped like the standard trades template map fully automatically; custodian exports map most columns automatically and leave the rest for you in the mapping table, where the target dropdown offers the TC fields (required ones are marked).
Locking. A TCA pipe will not lock until the required fields are mapped: Trade ID, Portfolio Code, Transaction Type (buy/sell), Trade Timestamp, Trade Currency, and Quantity or Nominal Amount. The lock error names anything missing.
Where to see them. Transactions never appear in the Fund Explorer (they are trade facts, not golden-record entities). Browse them on the Transactions page (sidebar → Data → Transactions): it follows the working Asset Manager picker and filters by fund code, ISIN and trade-date range, with buy/sell counts and a warning count for trades missing an arrival timestamp (those follow the implicit-cost path). Fund codes link to Explorer entities where a mapping exists (a registered identifier such as the product code, or a fund entity with the same name) — the Transactions and TC Readiness pages then show the fund's name with a link into the Explorer, and the fund's Explorer view links back to its transactions; unmapped codes simply display as-is.
After a run. The pipe detail page shows an Ingested transactions panel with the latest rows (side, quantity, currency, price, trade date). Like the Transactions page, the panel is served by the Regulatory Calculations service — a deployment without that service hides it (ingestion itself is unaffected). Rows that fail a required field are reported per-row in the run's errors — the rest of the file still loads. Reverting a run hides its transactions exactly like reverting a normal ingest; Restore brings them back. A reverted run's trades never block re-running the same file — fix the pipe config, re-execute, and the corrected rows land (an ACTIVE duplicate still skips); after such a re-ingest, restoring the old run is refused rather than duplicating live trades.
Large trade files load in bulk automatically. A plain-CSV trade file of 50,000 rows or more whose columns all map directly (no lookups or transform rules — the standard enriched-export shape) executes through a streaming bulk path: the file never sits in memory, and hundreds of thousands of rows load in about a minute with identical de-duplication and error reporting (a row missing a required field still fails only that row; a bad optional cell is dropped with its error recorded). Files that don't qualify simply use the normal path — nothing to configure either way.
The same automatic bulk path covers the high-volume dated-series feeds — NAV/valuation histories, dividend/distribution series and holdings snapshots. A plain-CSV file of 50,000+ rows on a valuation, dividend or holding pipe streams to disk and loads via a set-based merge with identical results to the normal path (same normalisation, same entity routing, same change detection: unchanged values confirm rather than duplicate), so multi-million-row price histories load in minutes instead of hours. Golden-record recomputation for such runs happens in the background and catches up progressively over a few minutes after the run completes.
Everything else works as on normal pipes: lookups (e.g. mapping an account number to a portfolio code, or a settlement type to BUY/SELL), transform rules, quarantine, and per-run history.
ADL figures and parameter pipes. Two further transaction-cost pipe kinds sit alongside trade pipes. An ADL pipe ingests anti-dilution levy figures — one row per fund (optionally per share class) with a validity period and an amount, as typically supplied by a custodian. Duplicate figures (same fund, period and source) are skipped structurally, so replaying an extract can never double-count — but a REVERTED run's figures don't block re-ingestion, so the revert → fix → re-execute loop works here too; when the file carries no source column, the pipe's client label is stamped as the source. A parameters pipe loads transaction-cost configuration rows (a parameter group, one or two key levels and a value — e.g. supplier precedence or fund lists); because parameters are configuration, re-loading a sheet updates rows in place rather than skipping them. Funds on an agreed scope-exclusion list (rows in the AdlScopeExclusion parameter group, one fund code per row) have their ADL figures marked excluded at ingest — the rows are kept and visible with their reason, never silently dropped. Locking is family-aware: an ADL pipe needs the fund code, both validity dates and the amount mapped; a parameters pipe needs the group and first key. Reverting a run hides its ADL figures like any other ingest; reverting a parameters run disables the rows it wrote (it does not restore previously overwritten values).
AuM and holdings feeds are normal pipes (no TCA option). The swing-pricing and look-through inputs live in the platform's canonical data model, not a side store. Upload an AuM-details sheet normally (do not tick the TCA option) and it builds a standard valuation pipe automatically — fully deterministically, no AI: fund-level AuM lands on the fund's {fund}:{date} series, per-share-class AuM on each share class's dated valuations, and the flow measures (units issued/cancelled, pre/post-swing prices, ADL amounts, fair-value mid, allocation %) on platform fields alongside them. The portfolio code keys the fund entity, so trades and cost calculations join to it directly. A dated-holdings sheet likewise builds a standard holding pipe: per-instrument market values, weights, asset-type classifications and the indirect TC/OGC look-through percentages, dated by the as-at column. Everything is visible in the Fund Explorer with full provenance, corrections arrive as new events (history preserved — reverting a run hides its rows like any other ingest), and the trades⇄holdings cross-check reads these same canonical holdings. Two legacy TCA pipe kinds (share-class flows and holdings (look-through)) used to cover these feeds with a separate store; existing pipes of those kinds keep working, but new ones can't be created — upload the sheet normally instead.
Expenses pipe (ongoing-charges inputs). An expenses pipe loads fee and expense figures — one row per figure (management, administration, custody, audit, distribution, performance fee, entry or exit charge) for a fund or share class over a period. Each row carries the fund code, the category, the period bounds and the figure as a monetary amount or an annualised rate (at least one of the two; the amount wins when both are present, and the row records which basis was supplied). Categories are stored as supplied, lightly canonicalised ("Management Fee" and "management-fee" land identically); the ongoing-charges calculation classifies ongoing vs one-off vs incidental from them. Expense figures are restatable facts: re-sending a corrected file updates matching rows in place (last write wins per fund, share class, category, period and source), and the pipe's client label is stamped as the source when the file carries none — so one expenses pipe per accounting feed gives a stable restatement key. Locking needs the fund code, category and both period dates mapped, plus the amount or the rate.
Benchmark price and FX rate pipes. The market-data inputs behind the arrival-price calculation: a benchmark prices pipe loads a price tape — one row per instrument per date, keyed by instrument, date, price type and source — and an FX rates pipe loads daily conversion rates per currency pair. Both are restated in place on re-send (last write wins per key), every price source is kept side by side (which supplier wins is precedence configuration for the calculation, never an ingest decision), and rows are always stamped with the working Asset Manager. Standard fair-value price tape (FVPT) files — the pipe-delimited, numbered-column convention — are recognised automatically and map fully without AI; when a tape carries no price-type column, fair-value mid is stamped, and when it carries no source column, the pipe's client label (prices) or "pipe" (FX) is. Locking needs the instrument identifier, price date and price value mapped (FX: both currencies, the rate and the value date).
UK CCI Disclosures — from calculation to released product summary. The UK CCI Disclosures page (sidebar → Regulatory → UK CCI Disclosures) turns an approved UK CCI calculation set into the released disclosure. It follows the AM picker: with an Asset Manager selected, the readiness board, the disclosure sets and the assemblable calculation sets are that Asset Manager's alone, narrowed by the server; choose "All asset managers" for the whole estate you can see.
The readiness board — do one thing: get the minimum data in. At the top of the page, the Readiness board states the CCI minimum-data specification and each fund's state against it: the NAV/price series, fund AuM, expense rows with categories, the classification facts, the four narrative sections, and the disclosure set itself. Each requirement shows a green or amber dot; hover (or expand the fund's row) for the exact ask — "no fund AuM series — request assets-under-management from the administrator", "answer the missing classification facts (or extract them from the prospectus via Documents)", "approve the set (a second person — the assembler cannot)". The header carries the estate rollup — N of M release-eligible (release-eligible means exactly one thing: a current RELEASED disclosure set exists — never "compliant") — plus a count of funds blocked per requirement. Everything on the board reads what the platform already stores; nothing is recomputed, so the board can never disagree with the engines. The one deliberate exception is the pre-release drift watch: a fund whose newest set is APPROVED (or staged) gets its calculation binding verified with the same check the release gate runs — a set the gate would refuse shows approved — binding stale, re-run before release the moment the book moves, instead of surprising you at the release click. Fresh bindings say nothing; an unverifiable binding makes no claim either way. With no funds in scope yet the board says so and points at ingest — it is the first screen to read after data lands. Paste the calculation set's id and Assemble disclosure set: the platform builds one disclosure set per share class — every required element of the product summary resolved to its one governed source (the calculated figures, the fund's canonical name and identifiers, approved narratives, the classification facts) and every element that does not apply stated as not applicable with its reason, never silently omitted. Elements that cannot be resolved are gaps, in words — the panel says exactly what is missing and where it comes from, and an unanswered classification fact is named as unanswered rather than guessed either way. The set then walks a governed lifecycle: Validate (refused while any gap remains), Approve (a different person than the assembler — the platform refuses self-approval), Release (re-verifies that the calculation inputs have not changed since approval — drifted inputs EXPIRE the approval — then builds the release bundle: the rendered product summary, the machine-readable CSV and JSON, each fingerprinted, all carrying the same content hash; releasing supersedes the prior live version, and two live versions of the same document are structurally impossible). View document shows the product summary rendered from the released set alone — a set below release grade carries a DRAFT watermark — and Machine CSV downloads the Kairo CSV v1 rows a distributor can consume today. Every transition is recorded with who, when and why.
Production release is off (build unit U1). Under the approved determination of 16 August 2026, releasing — a disclosure set, a calculation run, a legacy document, a filing hand-off, or the acceptance of a republish proposal — is refused in every environment, and there is deliberately no switch, role or setting that re-enables it — the one governed door is the recorded release-policy manifest described under Turning release on below, and without one release stays off everywhere. Everything up to Approve works exactly as before. In an isolated demo or sandbox environment the page offers Simulate release instead: it produces a DEMO bundle — the product summary, machine CSV, machine JSON and PDF, every format watermarked "DEMO — NOT FOR DISTRIBUTION" — stored apart from real releases (a DEMO bundle cannot be promoted into a release; there is no path), published nowhere, and downloadable from the set's DEMO bundles panel — you see and download only DEMO bundles belonging to your own asset managers. The set itself stays APPROVED. Previously released documents keep serving their sealed bytes exactly as before. Each deployment carries a signed environment identity (production, pre-production, sandbox, local demo); when a workload's identity cannot be verified the platform fails closed — regulatory serving surfaces refuse and say why, and ordinary (non-CCI) work is unaffected.
Approving is a named human with recorded authority (build unit U2). Approval derives from the signed-in person, never from anything a caller types: a service key, an automation or an AI agent is refused outright ("service identities, automation and silence cannot approve"), and the recorded approver is always the authenticated person's own identity. Beyond being human, the approver must hold an active manufacturer approval authority — the Approval authorities panel on this page (administrators only) is where a named person is granted authority for the manufacturer's whole book or one fund, and where a grant is revoked; one active grant per person per scope, every grant and revoke recorded with who and when. Until someone is granted authority, nobody can approve — the refusal says exactly that. Separation of duties is stated precisely: when the set was assembled by a human, a different authorised human must approve; when it was assembled by a service or AI principal, one authorised human approver suffices and the maker's principal type (and its human sponsor) is recorded on the approval event. Administrators can also pull the historic auto-approvals report — every set the platform itself moved to APPROVED under the old empty-diff grounds, with whether it went on to release; history is reported, never rewritten.
Controlled facts (build unit U3). Underneath the disclosure workflow sits one governed store for every regulatory judgment and product fact the platform relies on: a controlled fact names its subject (the whole manager, one fund, one share class, or an enumerated population), carries a typed value or an explicit "unknown" (a recorded unknown is a first-class answer, not an absence), an effective window, its source, and exactly one assurance basis — a judgemental fact is proposed by anyone (people, services or AI agents alike) but becomes usable only when a manufacturer-authorised human approves it (the same authority register and separation of duties as set approval); a fact flowing from an approved source mandate is approved by the mandate itself — no per-row signature exists, and none is pretended; a derived fact's assurance is the pinned method version that computed it. Asking the store a question returns one of six honest states — approved, proposed, unknown, in conflict, absent, or expired — each with the exact next step, and only "approved" permits release: a calculation or disclosure needing a fact in any other state is blocked in words naming the fact, its state and the ask. Two live sources disagreeing puts the fact in conflict — neither side silently wins; a person resolves it with reasons. Every change is a new version (history is append-only, values are never rewritten), and asking as of a past date answers with what was approved then — a later approval never rewrites an earlier answer. The API lives under the CCI fact endpoints; later build units surface these facts in the classifier and readiness screens.
Source mandates (build unit U4). The manufacturer approves sources, never individual rows: a source mandate records, per provider and data domain, the source system, what it covers (products, fields, periods), the approved mapping and its schema fingerprint, coverage and reconciliation expectations, how gaps, estimates, corrections and restatements are treated, the effective window and review date, and the evidence behind it. Anyone — including an AI proposing a mapping — may propose a mandate; a manufacturer-authorised human approves it (the same authority ladder as everything else), a new version supersedes the old, and revoking one is a named human act with a recorded reason. Asking after a source answers active, proposed, absent, expired or revoked — with the exact next step, and a passed review date flags the mandate for re-affirmation rather than silently killing the source. Two rules bite everywhere: a calculation or mandate-sourced fact can never ride a source whose mandate is absent, expired, revoked or unapproved (the refusal names the state), and an unchanged approved schema under an unchanged mandate needs no new approval while schema drift always does — a drifted feed is re-approved by a person, never silently re-mapped. A mandate-sourced controlled fact inherits all of this: if its mandate later lapses, the fact stops answering "approved" from that day forward, while historical questions still get the answer the mandate gave then. The Source mandates panel on the CCI page lists every mandate for the selected asset manager — provider, data domain, version, status, effective window, review date, who approved or revoked it — and an approved or proposed mandate is revoked there by a signed-in person typing the reason beside it (a service identity cannot revoke).
The classifier (build unit U5). The Classifier panel on the UK CCI page answers the boundary question for one fund (or one share class) for one act date, and always with exactly one outcome: route elsewhere (a non-UK channel, no UK retail availability, or a lawful legacy document — no UK CCI output is produced for that act), blocked (something legally required is unresolved — the legal route, retail status, required data, anti-dilution classification, the risk route, a required responsibility allocation, the release approval or the completeness gate), unsupported v1 (structured products, performance fees and carry, the NURS legacy route — a Kairo capability limitation, not an FCA prohibition), decision required (a judgment the rules expressly permit is still outstanding — a monthly-pricing justification, simulation methodology, benchmark appropriateness, the representative-class judgment, a permitted risk adjustment), or eligible. Routing is decided before the UK CCI lane; inside the lane the precedence is fixed — blocked beats unsupported beats decision-required beats eligible — and anything that fired but was out-ranked is shown as "also firing, out-ranked by precedence", never silently swallowed. Every condition names its reason code, what it found, and the exact ask; every condition reads the governed controlled facts (nothing is inferred from a fund's name or an identifier), so the fix is always "resolve the named fact", and an eligible answer says plainly that eligibility does not itself prove production readiness. Every classification is recorded — the refusal record — which the onboarding coverage report later aggregates across a whole book.
Short track records: simulate before benchmark (build unit U13). When a fund's history is shorter than the ten-year DISC 5.2 track record, the risk score walks a fixed ladder — actual history, then the manufacturer's DISC 5.2.2R simulation, then the appropriate benchmark — in that order, never skipped. The platform accepts a manufacturer-supplied simulated series through the governed ingestion route: the methodology lives as a controlled fact an authorised human has approved, and a declared fill date makes the simulated portion visible on every score ("history before this date is the manufacturer's simulated series"). The benchmark alternative opens only after two recorded decisions: the manufacturer's determination that simulation cannot reasonably be performed, and an approved benchmark-suitability assessment — without them the score simply says which decision is outstanding, and defaulting to a benchmark is impossible. Two honest endpoints remain: under five years of history the DISC 5.5.1R(6) floor of at least 9 carries the score automatically and visibly; between five and ten years with no appropriate benchmark the score blocks outright, saying that FCA guidance or a waiver is required — the platform never invents a method.
The legal branch and the NURS hard block (build unit U6). A fund's legal route is recognised from two declared facts — its legal structure (UK UCITS, NURS, or other) and, for a NURS, whether it relies on legacy KII documents during the transition — never inferred from a name or an identifier. Resolve legal branch derives the route for an act date and records it as a governed derived fact: a UK UCITS goes straight to the DISC product summary; a NURS electing the CCI route is a determined, supported matter; "other" routes out of UK CCI scope; and an undeclared structure simply refuses with the ask. A NURS relying on its legacy documents is different: whether that route validly continues is an open legal question with external counsel, so the platform holds it as unsupported in version 1 and hard-blocks it in three places — calculation (no risk score computes), publication (no disclosure set assembles), and delivery (its bundles never reach the delivery feed, and the withholding is disclosed, never silent). The block lifts only when counsel answers and the branch returns through its own gate — nothing on the platform can override it in the meantime.
Who it may be sold to, and what the FCA says it is (build unit U7). Two scope facts the classifier's routing depends on stop being assumptions and become governed answers. UK retail distribution status is recorded per share class and per distribution channel (direct, adviser, platform, discretionary — a closed list) as retail, non-retail exempt, or an honest unknown: claiming a channel is non-retail exempt demands stored evidence on all three limbs — the prospectus restriction, the restricted communications, and the distribution arrangements — and, like every judgemental fact, a manufacturer-authorised human approval before it counts. The classifier's "not available to UK retail" route-out derives only from these facts: any approved retail channel makes the fund retail-available; all channels approved non-retail exempt routes it out of the UK CCI lane; and an unknown blocks exclusion — while any channel is unresolved the derivation refuses, names exactly which channels are open, and the share class simply stays blocked, which is the honest state (a fund can never slip out of the regime through an unanswered question). FCA authorisation status enters as a structured record from an evidenced source — status, FCA reference number, the exact source and retrieval date — riding a source mandate that is both active and designated: official FCA-register data needs no row-by-row attestation, but only under a mandate that names the register as authoritative for this field. If the prospectus or product master ever disagrees with the FCA-sourced record, the fact goes into conflict — neither side silently wins, and release stays blocked until a person resolves it with reasons. The registry element for the DISC 4 authorisation statement now resolves from this governed fact; its old unexplained "platform" sourcing is gone.
Finding the unsupported routes (build unit U8). The three version-1 refusals the classifier enforces — structured products, direct performance fees, and indirect performance fees or carried interest — fire on governed facts, and Unsupported-route detection makes those facts resolvable from evidence instead of blind declarations. The sweep reads only what the platform already holds: the fund's active governed classification decision (a declared non-linear payoff, a structured-deposit fact or a non-linear category conclusion are characteristics that prompt the Handbook structured-product question — a non-linear payoff is expressly not treated as the legal test itself) and the fund's expense categories, swept with the very same performance and carry token families the cost calculators count, so detection can never disagree with the money path. Where characteristics are found and the corresponding fact is unresolved, the sweep proposes the fact with its evidence attached — it lands as a proposal, and only a manufacturer-authorised human approval makes it count. Two honesty rules are absolute: an empty sweep never declares a fund clean (absence of evidence is not a declaration of absence — the manufacturer answers the undetected questions themselves, and until all three are resolved the required-data gate keeps the share class blocked), and detection never overrides a resolved answer — a human's approved yes or no outranks any sweep. Once approved, each detection-raised fact drives exactly the refusal it should: the classifier answers unsupported in version 1 with the route named, a capability limitation stated plainly rather than an FCA prohibition.
Entry and exit costs from the charging terms (build unit U9). The disclosed entry and exit figures now come from the share class's contractual charging terms — never from receipts over fund assets, a basis the determination retires outright with no silent fallback: a fund without approved terms simply refuses, in words, until they are recorded and approved. The terms are governed facts per side (entry and exit), each carrying the contractual charge basis — a simple percentage, a flat amount, a tiered schedule, a capped rate, an evidenced effective rate for genuinely varying or waived charges (grounded in investor entry and exit activity), an explicit zero, or not-applicable with its reason — plus conditions, waivers, effective dates and the source document, approved by a manufacturer-authorised human like every judgemental fact. Application follows the prescribed bases: £10,000 for an ordinary CCI, £1,000 invested annually for a regular-premium CCI, and every side always produces both the percentage and the cash amount, each basis by its own arithmetic — a flat £250 is 2.5% of £10,000 but 25% of £1,000, a tier is the band the assumed investment actually falls in, and a binding cap is disclosed as such. Zero is handled honestly: a known, approved zero charge is a complete answer that computes as 0.00% and £0 — it never blocks the calculation. What the manufacturer must separately hold is the written zero or not-applicable confirmation to distributors (DISC 6.3): a missing confirmation blocks the distribution output — the disclosure set will not assemble — while the refusal says plainly that the underlying cost fact is complete, so the two failures can never be confused.
Transaction-cost windows and certified aggregates (build unit U10). The transaction-cost figure's window basis is now resolved by a declared register, never by accident: the standard basis is the annualised average over the previous 36 months, a product operating for less annualises over its own window with the scaling named on the run, and the recognised exceptions — a product whose own contractual term is under 12 months, a new product with no history to annualise (the estimate basis), a declared determination that future costs will differ materially, and fixed or known subsequent charges — each carry their rule reference and resolve by a declared precedence: when more than one applies, the record names which one won and shows the out-ranked ones visibly, and every resolution rides the run's findings as provenance. The trade-level figure keeps its gross treatment — explicit costs with nothing netted. A provider-calculated certified aggregate may serve as the presented figure, but only as a governed input: it must carry the complete DISC 6.5 evidence set (provider, methodology statement, period covered, calculation date, figure and coverage) and ride an active source mandate, and it is permanently described as a provider-calculated input — not a safe harbour: responsibility for the disclosure stays with the manufacturer, and the run says so wherever the aggregate is used.
Anti-dilution, classified honestly (build unit U11). Every anti-dilution amount is now one immutable cash-flow identity — recorded once with a stable reference, and structurally impossible to record twice, so the same pound can never land in two figures. Each identity carries five orthogonal attributes — who paid, who received, what triggered it, by what mechanism, and for what purpose — none implying another, plus a separate fund-benefit attribute (whether the fund received a benefit, independent of who paid) and a separate EU treatment held apart from the UK's. From those attributes the platform derives exactly one UK numeric category: a market tax or trading levy paid sits positively inside the gross transaction-cost figure; a dilution levy borne by the investor on subscription or redemption is an entry or exit cost, linked to its identity; a benefit received by the fund has no UK numeric category at all — it is never deducted from the figure and never used to floor it (separate presentation is optional). A single cash flow that is both investor-paid and fund-received is one identity with one category and the benefit attribute set. Anything with missing attributes is unresolved and anything fully recorded that fits no defensible treatment is quarantined for a person — either way the classifier blocks approval and release, and the screen shows exactly Classification required — release blocked, never a provisional figure with confident wording. The UK copy now matches the engine everywhere: the UK figure is gross — nothing is netted, and classified market taxes carry the exact sentence "The transaction-cost figure includes £X of market taxes and trading levies paid." — while the EU route keeps its own prescribed deduction treatment, floored at the explicit transaction costs (there is no zero floor). The UK rule-pack description is corrected by a new superseding version; the prior descriptions remain retrievable exactly as published.
Pricing cadence, governed (build unit U12). The risk score's pricing frequency now follows DISC 5.2.1R's ladder with governance rather than inference alone. Weekly is the rule's grain: native weekly observations are the base case, and where daily prices exist the weekly series is derived from them — weekly is created, not waived, and no exception is involved either way. Monthly pricing is lawful only behind a recorded exception: a governed fact carrying the reason, the sources actually approached for weekly data, the affected history and the evidence, approved by a manufacturer-authorised person. Three families of reason are refused at the door and never appear as options: cost or licence fees, convenience, and "that is the file we received" — none of them says weekly data cannot be obtained, only that nobody asked. A monthly series with no approved exception blocks release, with the ask named; with the approved exception, the score computes and its provenance carries the reason, the evidence and the approver on the run itself. The classifier reads only the derived facts — a monthly fund with the justification outstanding sits on decision required until a person records and approves the exception.
The risk route, governed (build unit U14). Which DISC 5.5 route carries a share class's risk score — standard, preset, or adjusted — is now a derived, governed answer rather than a hand-typed flag. The derivation reads the fund's active governed classification decision: where a DISC 5.5.1R categorical preset applies (a structured deposit scores 1; CFDs, contingent convertibles, specified derivatives, VCT/EIS holdings, possible loss beyond the investment, significant leverage or slower-than-monthly pricing take at least 9), the route is preset and the record names exactly which predicates fired; where nothing categorical applies and no adjustment is in play, the route is the standard base case; and a fund with no classification decision simply refuses — a route is never guessed, and the classifier keeps the class blocked until one derives. A permitted risk adjustment exists only as a recorded decision: the adjustment value, its rationale and the evidence are proposed together — a rationale-less adjustment cannot be expressed — and a manufacturer-authorised human approval binds to exactly that version, so the rationale is recorded at approval time and retained forever in the append-only fact history. An adjustment that is proposed but not yet approved holds the class on decision required; once approved, the risk engine applies the governed value in place of the legacy configuration parameter, and the run's provenance carries the adjustment, its rationale and its approver. The engine's existing guard is untouched: a downgrade where a DISC 5.5.1R preset applies remains disclosed and refused as not normally appropriate.
Share classes: per class by default, anything else by decision (build unit U15). One product summary per share class is the default — it needs no decision and no configuration. Presenting classes together, or letting one class stand for others, is a recorded decision under the DISC 2A.4.6R–2A.4.9R conditions, approved by a manufacturer-authorised person like every judgemental fact. Combining classes of the same CCI demands the recorded basis that the combined presentation is clear, fair and not misleading, plus how the class-specific differences are presented — and at least two classes; materially different charges often make combining inappropriate, and a decision that cannot say why it is fair does not exist. A representative class demands the selected class, the classes it represents, the rationale and the conditions record together — a representative-class decision without its recorded conditions is refused outright — and the record stamps its three-year retention date at proposal time (the platform's append-only history keeps it beyond that; the stamp documents the DISC minimum), with every representative decision listable on demand with its rationale, approver and retention date. The classifier reads only the derived facts: no decision means the per-class default; a representative decision that is proposed but not yet approved holds the class on decision required until a person approves it.
Regime isolation at the output boundary (build unit U16). Every derived calculation result now carries a lane identity — UK CCI, frozen UK legacy, or current EU PRIIPs among them — and each output adapter accepts exactly its own lane: the UK CCI product summary composes only from UK CCI results, the EU KID only from current-EU results, and any result offered across lanes is refused at the boundary in words naming the adapter, its lane and the result's. The refusal is pairwise across every combination of lanes, not merely UK-versus-EU, so a cross-border fund holding a frozen UK legacy document and a current EU KID for the same product at the same time composes both documents with neither contaminating the other. Results computed before the lane stamp resolve their lane from their recorded regime; the calculation-set level regime check continues to guard everything else.
Outputs that provably agree (build unit U17). A released disclosure's four artefacts — the product summary, the PDF, the machine CSV and the machine JSON — all come from one sealing act over one approved version, and the platform now proves they stay that way. Reconciliation checks three things on demand: every artefact's stored bytes still match the hash sealed at release (and the bundle manifest agrees), the two machine artefacts agree value-for-value — the CSV and JSON are parsed and every field compared, so the same slot can never say two things in two formats — and the bundle is complete (the summary, CSV and JSON always present; the PDF present or its absence disclosed in the manifest itself). A failure blocks: an artefact mutated after generation is refused at the download boundary — the platform will not serve bytes that no longer match their seal — and a failed bundle is withheld from the delivery feed with the withholding disclosed, never silently dropped and never silently served.
Batch approval on a full enumeration (build unit U18). Approving disclosures in bulk never weakens a single gate. A batch approval submits an immutable manifest enumerating every candidate in scope — the set and the exact content fingerprint the approver reviewed. If the scope holds a validated set the manifest does not name, the whole batch refuses: nobody approves "everything" without naming everything. Partial approval is a first-class decision — the approver may approve an explicit subset, and every candidate left out is recorded per-candidate as deferred, never silently omitted. Every approval passes through the individual gate, one at a time: the same authenticated-principal check, the same authority register, the same separation of duties, one event per set — the batch merely organises them. A set whose content changed between enumeration and execution is refused by name — the approver never signs content they did not review — and the existing release protections stand untouched: a set whose inputs drift after approval has its approval expired at the release gate, and the 60-day document-freshness boundary still blocks stale track records at the moment of release. The batch itself is durable: the manifest a person was shown, what they did with every candidate, who they were and when.
The onboarding coverage report (build unit U20). For a whole client book, one question matters commercially: how much of this estate can go on the UK CCI lane today, and what exactly stands in the way of the rest? The coverage report answers it by running the real classifier across every fund in the manager's book (the same universe the readiness board reads, with the sweep cap disclosed whenever it bites): counts per outcome — eligible, blocked, unsupported in version 1, decision required, route elsewhere — a condition histogram naming every reason that fired anywhere in the book with its frequency and affected funds (the estate's blockers as a worklist, not a mystery), and a per-fund row naming each fund's outcome and its triggering condition, so the next step is always written down. Every sweep classification persists as its own refusal record, one failing fund never stops the rest of the book, and the eligible count carries the classifier's own caveat: eligibility does not itself prove production readiness.
Turning release on — the release-policy manifest (S0b). Real UK CCI release is off by default, everywhere — and the only thing that can ever turn it on is a recorded release-policy manifest: not a flag, not an environment variable, not a role. A manifest is scoped — it names the environment it licenses, the asset manager it covers, and the dates it runs — and it is granted by a named human holding recorded manufacturer approval authority (the same authority register that governs every approval; a service identity can neither grant nor revoke one). Release is enabled only while every part matches: the workload's attested environment identity equals the manifest's environment, the asset manager matches, and today falls inside the effective window. In every other state — no manifest, expired, revoked, not yet effective, wrong environment, wrong asset manager, or an unknown environment identity — release refuses, with the exact state named in the refusal. Revocation is a named human act with a recorded reason, is confined to manifests covering your own asset managers, and takes effect immediately: the gate reads live state on every release act, so there is nothing to cache, restart or flush. The manifests and the live verdict for the current environment are visible on the Release-policy manifests panel of the CCI page (with an asset manager selected in the top bar): grant one there — environment, effective dates, optional notes — and revoke an active one by typing the reason beside it; the panel header says whether release is ENABLED or OFF here, and why.
The executed schedule as a release precondition (build unit U19). Before anything releases for a client, the platform checks that an executed CCI Services and Responsibility Schedule record exists and is current — the record names the execution date, the effective window, the parties, whether responsibilities are allocated between them, and where the executed document lives, and it is recorded by a named human with recorded authority. A missing or expired schedule blocks release outright, with the state named (no record; expired on a stated date; not yet effective). Where the schedule's parties are two or more manufacturers, a responsibility allocation is required — the classifier's collaborating-manufacturer condition derives only from the current executed record: a schedule that allocates responsibilities clears it, one that does not holds the book blocked, and no record means nothing is derived at all — an allocation is never assumed from silence. What the schedule says is counsel's work; that a current executed record must exist before release is the platform's, and it is enforced today.
The go-live pack. Two artefacts close the loop for a client going live. The go-live readiness report answers, per client, on one screen: is the environment's identity attested, is a release-policy manifest active for this environment and manufacturer, is the executed schedule current, is at least one person granted approval authority — each check read from the very gate that enforces it, never a parallel copy — plus the classifier's counts across their whole book (eligible, decision required, blocked, unsupported in version 1, route elsewhere). Green means every precondition holds and the client can publish what the classifier marks eligible; anything amber names its own ask in the gate's own words. The evidence pack is the document a client's compliance officer asks for: every Gate 2 case with its expectation, the tests that own it, their results from an actual executed test run, the rule references the owning modules cite, and the mutation-proof convention stated — generated by a script from the test run, never written by hand, and regenerable from any run.
The estate at one operator's scale. The Estate acts bar runs the whole book in one governed act — always dry-run first: the preview is a ledger (per fund: what would happen and why), and Apply executes exactly what the preview showed. Assemble estate builds and validates disclosure sets for every fund with an approved UK CCI calculation set; it is idempotent by calculation binding — a fund whose live sets already carry the driving calculation's binding is skipped, so re-running the sweep can never mint duplicate versions — and one fund's failure never stops the rest. Approve by exception looks at every VALIDATED set and compares it field-by-field against the prior approved version of the same fund, class and language (the same canonicalisation the content fingerprint uses, so the comparison can never disagree with the hash). It triages; it does not approve. A set whose diff is empty is reported as no content change; everything else — a figure written differently included — surfaces with its side-by-side field diff. There is no tolerance policy and nothing the platform evaluates on your behalf. In both cases the set stays where it was: a named, authorised person at the manufacturer approves every new version of a disclosure, and the platform never approves one on their behalf. That is the UK CCI Compliance Determination of 16 August 2026 speaking, and it is enforced in the API and the lifecycle itself, not merely hidden in the screen — the platform previously could self-approve an unchanged version, and no longer can. What the triage buys you is the reading, not the signing: at estate scale it tells one reviewer which handful of documents actually moved.
Every act writes one audit line plus each set's own event trail (with the batch reference), and disclosure-set transitions now appear on the Audit Trail's governance ledger alongside runs and packs.
Once released, downloads are the sealed bytes. For a RELEASED (or superseded) set, View document, Machine CSV and PDF serve the release's own sealed artefacts — byte-for-byte what was published, fingerprints intact; regeneration on download is impossible. The Released documents panel lists the bundle's four artefacts with their sizes and sealed downloads, and shows each one's home in the document subsystem: on release, every artefact registers automatically as a proper fund document (one document type per artefact — product summary HTML, product summary PDF, machine CSV, machine JSON — named TYPE_ISIN_language_GB_date_vN, subject-linked to the share class, jurisdiction GB). Registration gives each artefact permalink-to-latest (/d/… — always the current released version; the same link keeps working across re-releases) plus an immutable versioned evidence link (/dv/… — this version's bytes forever, even after supersession). Both links serve only once the document is made public in the Documents screen — nothing is published automatically; registration lands restricted, honouring the platform's standing rule. A registrar outage never blocks a release: the failure is recorded per artefact on the panel and Register documents retries idempotently (identical content never mints a duplicate version). Registered artefacts are findable on the Documents screen — and through the partner document API — by type, country, language, fund/ISIN and now publication date.
TC Readiness — the calc handover. "Ingest done" for transaction costs is defined per fund per quarter: every input clean, complete and calc-ready. The TC Readiness page (sidebar → Regulatory → TC Readiness) shows that gate — pick a quarter and each fund in scope gets a Ready / Not ready verdict (the table pages at 25/50 rows) built from five conditions: trades present in the quarter; benchmark-price coverage (every instrument/date needing market data has an active price, tenant or shared); FX coverage (every trade currency/date missing a rate on the trade has one); the anti-dilution expectation (four distinct states — see below); and validation findings (Market-on-Close funds with trades missing an arrival timestamp are flagged as warnings — warnings never block, only blocking findings do; and a MOC fund whose trades carry an arrival stamp materially before execution — an order timestamp where the MOC convention expects the first fill — raises an advisory: disclosed on the fund and per trade on the exception grid, never gating readiness and never opening a case, because the first-fill switch happens in the client's upstream extract — the platform holds the MocFund list and checks the convention is honoured). Expand a fund to see exactly what is missing — the missing benchmark-price and FX lists double as the market-data shopping list — and load the per-trade exception grid (one row per trade per finding, capped at 1,000 with truncation flagged) for the trade-level view a migration is compared against. Readiness never blocks ingest: files always land; the gate tells the downstream calculation platform which fund/quarters are safe to consume.
The ADL column — four states, not two. Whether a fund is supposed to carry anti-dilution figures is derived from the fund's own data, not assumed: an AdlExpected parameter row is the explicit override (value true — or blank, the historical opt-in — means figures are required; value false declares no levy), otherwise the fund's own OFST454300 (Has Dilution Levy Applied By Fund) declaration answers it. The column then shows one of four states: not applicable (declared no levy — passes, with the source shown, e.g. “no dilution levy · from data · OFST454300”); satisfied (expected and figures present — “present · N figures”); missing (expected and none held — RED, blocks readiness: the offset cannot be computed); and not declared (nothing states either way — AMBER: it still passes, but it is visibly unanswered and named in the findings as an advisory, because an undeclared fact must never render as a default that looks like an answer). Figures held by a fund that declares NO levy raise a warning finding — the data contradicts the declaration; chase one or the other. The calculation reads the SAME derived state (frozen into every new snapshot), so TC Readiness and a run's anti-dilution findings cannot disagree about the same absence.
Market data — fill the gaps. The shopping list can now be actioned in place: the Market data panel sits directly below the stat cards on the TC Readiness page (above the per-fund table, so it is visible without scrolling) and fetches exactly what readiness says is missing — the uncovered (instrument, date) price pairs and (currency, date) FX pairs — through the Asset Manager's configured provider, and writes the results into the same price/FX stores every other consumer already reads (rows arrive under source md:<provider>, so provider-fetched and tape-ingested data never collide and remain distinguishable). Configuration is the MdProviderConfig parameter group (key1 provider names the adapter; key1 credential_mode is kairo — our licence — or client — the Asset Manager's own entitlements; the credentials themselves live in the deployment's environment, never in parameters). Preview gaps shows the request counts without touching any provider; Fetch missing runs the fetch (audited) and reports business outcomes — pairs fetched, no-data dates, identifiers the provider could not resolve, and FX pairs skipped because the fund's canonical currency is unknown (the platform never guesses a quote base). Fetched rows flip readiness on the next check and join the frozen input set on the next snapshot — and because snapshots pin inputs, a value that changes after a freeze shows up as drift, exactly like any other input. A deterministic test provider (fake) exists for proving the flow; licensed provider adapters slot in as they are contracted — and a generic REST provider (rest) makes a client's own market-data API a configuration exercise: URL templates, field paths and auth mode ride the same MdProviderConfig group (validated on save, problems disclosed as findings), the secret stays in the deployment environment, and every call is SSRF-guarded with redirects refused.
Instrument references are bought once. Providers meter identifier resolution, so the panel keeps a per-Asset-Manager reference cache: each identifier's provider reference (the RIC, typically) is stored the first time it resolves — including negatives, with a re-check horizon, so an unresolvable identifier is not re-asked (and re-billed) every run. Seed references primes the cache from data the platform already holds — the static book's Reuters Code Of Listing and the trade feed's own RIC column — zero provider calls. The gap preview shows each sample pair's cached reference, the fetch summary reports how many resolutions came from cache versus the provider, and every fetched price row stores the reference it was fetched under, so "which RIC did this price come from" is answerable on screen.
Enriched per-trade cost files. When a source supplies transactions already carrying their cost decomposition — per-unit arrival-quote differences (vs mid/open/close), per-route implicit amounts, a fixed/spread-route amount, an explicit-fee total, fund-currency figures and cost percentages — the trade pipe stores every supplied figure loss-free alongside the raw trade facts. The standard per-trade cost export shape (camelCase headers such as ImplicitTCQuoteMidPrice, FinalIndTCInFundCCY) maps fully deterministically with zero AI, including registry skips for its display-duplicate name columns. Supplied figures never replace the platform's own arithmetic: the calculation cross-checks the supplied explicit total against the stored fee components and reconciles its computed total against the supplied per-trade totals, flagging any disagreement as a finding.
TC Readiness — the arrival-price column. Price coverage counts rows in the price store; the Arrival price column answers the question that actually decides whether implicit costs can be computed: what will the calculation's pricing waterfall resolve? It reads N of M priced · K zero — supplied per-trade amounts plus trades that can be computed against an arrival reference (their own quotes, the OTC tiers, or a stored market-data price in the same currency) — red whenever any trade would route to zero (no arrival price: the implicit cost cannot be measured for that trade — routed to zero, not measured as zero). Where the fund has a calculated run the column reports what the run actually did (marked run); otherwise it reports the engine's own join expectation (marked expected). The two sources can legitimately differ right after new prices land: expected flips first, and the run figure follows on the next recalculation.
Assessments — the obligation-first front door (sidebar → Regulatory → Assessments). Regulatory production starts from the obligation, not from a calculation: you state what you owe (a UK CCI product summary or an EU PRIIPs KID as documents; MiFID costs-and-charges data or DC pension cost transparency as data obligations), why now (initial, annual, material change, correction or monitoring — the review reason is also the only door to a parallel assessment), and as of when (one date; the system recommends the latest complete quarter end and states the 60-day prepare-by arithmetic that follows from it). The page then shows the calculation plan the rule pack derives — every required component with its own window as real dates and its rule reference: one-off and ongoing costs over the preceding 12 months, transaction costs over the preceding 36, the performance-fee illustration, the risk score's 10-year track record, the performance graph. You never compose a period; a cycle label like 2026Q2 resolves to its quarter-end date and is displayed as exactly that — a label. The two data obligations render their windows as platform conventions marked verification pending rather than showing a rule reference that has not been verbatim-verified. Approving the plan approves the derivation, never results — every freeze, calculation, validation and four-eyes approval still happens per fund under the same gates. The assessment's board shows the fleet: chain progress counters (frozen → calculated → validated → approved → released), then the funds that need a human first — each with its asks in plain words from the readiness composition — and each row opens the fund drill-in already scoped to the assessment's derived context. Creating a governed freeze from that context derives the gather window server-side from the regime's own convention; the request carries the as-of date, never a hand-built period. Run outstanding on the board automates the mechanical maker steps across the whole scope — freeze, open the run, calculate — and reports a per-fund ledger in words (one failure never aborts the rest). It deliberately stops at calculated: validating is the checker's step and approval a third party's, so the board tells you how many are now ready for the checker rather than pretending the judgement gates away. Scope is chosen from the live universe: filter the client's funds, tick funds or individual share classes (a class pick records the class on the assessment and brings its parent fund into scope — calculations stay fund-level), or paste a list of codes/ISINs.
Regulatory Calculations — the governed calculation & disclosure surface (sidebar → the REGULATORY group; previously one "Cost Runs" page under Quality). The regulatory production line is a first-class sidebar group, split by JOB rather than by section:
- Operations — the work surface: the cockpit as a full page (cycles calendar — with any non-compliant cadence flagged red on its chip — the work queue by role) with the open queues folded in as tabs — Approve by exception, Exception cases, Republish proposals, and Deadlines & doc reviews (registered document obligations sorted by due date: amber within 14 days, red overdue). Every row jumps to the fund's own page on the item's scope. The page carries the Operations agent ("What should I do first?") — it answers over the SAME rollup the panels render, fetched fresh per question, with a stated priority order (overdue deadlines → overdue cycles → blocking exceptions oldest-first → never-calculated funds → the role queues); it recommends, it never acts — approvals stay human, four-eyes.
- Calculations — the page described below: reporting context, the portfolio grid, and the shared fund drill-in (KID rail, calculation catalogue, snapshots & runs).
- Findings — the cross-fund analyst view: the book's findings for a chosen context (blocking first, then warnings; advisory disclosures are audit statements, not work), each fund a jump-link.
- TC Readiness — the calc handover gate (same job family, moved from Quality).
The dashboard policy that binds them: the dash carries rollups and jump-links only, never actions — every dash number reads the same endpoint as its section page, so they reconcile by construction. Bell = events, dash = state, section pages = work.
Calculations runs every regime — transaction costs, PRIIPs SRI risk, UK CCI risk, MiFID, DCPT — and carries the whole KID/CCI document workflow. This page takes a fund from frozen inputs to a released, auditable cost figure:
- Freeze a snapshot. Pick the fund — the list offers every fund with ingested transaction data for the working Asset Manager, with its trade count and date span (no data yet means no funds: build and execute a TCA trade pipe first) — then the regime (EU PRIIPS / UK CCI / MiFID ex-post / MiFID ex-ante / DCPT / PRIIPs SRI (risk) / UK CCI risk score) and period: a calendar Quarter, or the regime's own disclosure window ending on an as-of date — the window mode follows the selected regime automatically (PRIIPS and UK CCI 36 months, MiFID ex-post/ex-ante 12, DCPT 3), so a context freeze lands on its convention by default; a run whose window differs from the convention says so in its findings. Quarters remain available for every regime as the readiness-aligned interim view. Freezing pins the exact input rows — trades, prices, FX, ADL or config, and the fund's canonical AuM/currency series (the ratio denominator, from the ingested AuM-details valuations) — by id and hashes their content; the readiness verdict over that exact window is recorded at freeze. Because the AuM series is part of the frozen set, a restated AuM value now shows up as drift on Verify, exactly like a corrected trade. Re-freezing supersedes the prior snapshot; Verify on any snapshot re-gathers the current inputs and reports drift — a changed input after freeze is exactly the signal a released number must not survive.
What "ready / not ready" means. Readiness answers one question: does the platform hold every input this calculation needs for this fund and period? It is advisory, never a gate — a not-ready snapshot can still be calculated; the verdict is simply recorded as a finding on the result so nobody releases a number without knowing an input was incomplete. Click the ready/not-ready chip on any snapshot to see exactly what the check found, condition by condition, with what to do about each. The conditions depend on the regime. EU PRIIPS (the arrival-price method) needs: trades in the period; an arrival price for every trade that needs market data (from a benchmark-price tape, or costs supplied on the trade rows themselves); FX rates for trades missing one; anti-dilution figures where the fund is expected to carry them (the derived four-state expectation — see The ADL column under TC Readiness); and no blocking validation findings. UK CCI is deliberately lighter — trades plus eligible explicit costs on them; arrival prices, FX and anti-dilution are not required at all (the panel lists them as "not required" so the difference is visible). A common surprise: a fund can be red under PRIIPS and green under CCI from the same data. The MiFID ex-post / ex-ante and DCPT contexts use the PRIIPS input set without the anti-dilution requirement (listed as "not required" on their panels). The risk regimes (PRIIPs SRI and the UK CCI risk score) share a readiness different in kind: it is the series-integrity verdict over the fund's canonical NAV/dividend series (the same six conditions described under Series integrity below, aggregated per condition across the fund's share classes, plus a "series present" check that says honestly when there is no NAV series to compute from at all). To fix a not-ready snapshot: ingest whatever the panel names (each condition says which pipe kind supplies it), then freeze again — the new snapshot supersedes the old one. 2. Create run → Calculate. A run opens as DRAFT over a frozen snapshot. Calculate computes the figure from the pinned rows only — it refuses to run if the snapshot has drifted (re-freeze first) — and the methodology follows the run's regime:
- EU PRIIPS (arrival price). Each trade's implicit cost resolves along a price waterfall (supplied mid/open/close route amounts → supplied generic amount → computed from raw arrival quotes with buy/sell slippage signs → zero); negative per-trade implicit costs are retained by default — a per-client policy (the
TcNegativeCostPolicyparameter, pinned in the snapshot so it is always reproducible) can instead clamp each negative trade to zero, counted and disclosed. The total is then reduced by the fund's anti-dilution benefit (same three-route waterfall as UK CCI) but is never disclosed below the explicit costs alone — the EU floor: slippage and offsets can never undercut the fees demonstrably paid. Implicit, explicit and the offset used are all shown separately; when the floor binds the result says so. The same fund can therefore legitimately carry different UK and EU figures: the UK CCI figure is gross (nothing netted), while the EU figure nets the benefit but never below its explicit costs. For funds holding other funds, an indirect transaction-cost add-on joins the figure: each fund holding contributes its weight times a rate resolved along a waterfall — a reported per-holding rate on the holdings feed first, else the held fund's own disclosed figure from the golden record (EPT portfolio transaction costs, then the EMT ex-post figure — including figures this platform itself released), pinned in the snapshot like every other input. Fund holdings with no rate anywhere follow a pinned missing-data policy (TcLookthroughMissingPolicy): contribute nothing (the default) or take the average rate of the resolved fund holdings — either way disclosed. The published total is direct plus indirect, with both shown separately. Per share class, a share-class overlay applies: trades attributed to one class (a "share class ID" on the trade row — currency-hedging forwards for a hedged class, class-specific dealing) burden only that class, at its own average AuM, on top of the shared sub-fund costs every class carries; classes with no attributed trades simply carry the shared figure. The fund-level number is unaffected — the overlay is an allocation refinement, disclosed in the findings whenever it applies. - UK CCI (explicit costs, gross). Only explicit costs count (no slippage implicit), and the figure is gross: the UK rules carry no anti-dilution netting provision, so the fund's anti-dilution benefit — still derived along the same disclosed three-route waterfall (recorded levy figures from an ADL pipe → per-date levy amounts on the AuM-details feed → derivation from swing pricing) — is presented as information beside the figure and never subtracted. When a levy exists the result names it and states the gross treatment; any netting would be the firm's own documented decision, which the platform does not presume. There is no share-class cap and no securities-lending add-on under CCI.
A UK CCI run also assembles the full cost table when the snapshot pins the inputs: ongoing charges (expense figures from an expenses pipe over the latest 12 months' average AuM, plus the weighted look-through cost of underlying funds held — including closed-ended investment funds (investment trusts), the point the UK regime settled; the figure is shown with and without the CEIF component), plus one-off (entry, exit, subscription, redemption, initial, front-end, deferred-sales, back-end and switching charges — a plainly one-off charge is never counted as ongoing) and incidental (performance fees — never inside ongoing charges) buckets, each disclosed separately. One charge lands in exactly one figure: a category name carrying a performance token belongs to the performance-fee figure and never also to the entry or exit figure, and a one-off charge with no clear entry-or-exit side yet (a switching fee) is counted in the one-off bucket but excluded from the entry and exit figures with a finding saying so — placed once the firm's approved category vocabulary lands, never guessed. CEIF holdings are identified by an explicit flag on the holding or recognised classification codes; holding weights are auto-detected as percents or fractions from the composition sum — every choice is stated in the findings. A snapshot frozen before the expense/holdings inputs joined the frozen set still calculates the transaction-cost figure and says the ongoing-charges block needs a re-freeze.
- MiFID ex-post / ex-ante and DCPT (reporting contexts). The same arrival-price core under each context's window convention — ex-post over 12 months, DCPT over 3 (a 3-month ratio is disclosed as the period's own figure, never annualised) — with no anti-dilution offset, no fund-of-fund look-through and no share-class attribution (all stated on the result). Ex-ante follows MiFID's own rule: the trailing actual costs are the proxy when the fund has them, else the estimate below.
Which methodology runs is decided by auto-routing, and the decision is recorded on every run. The routing tree is regime → operating age → trade count → configuration override, and the result carries the full record — the chosen methodology, the inputs it was decided on, and the human-readable rule path — so "why did it calculate it that way" is always answerable from the evidence drawer. For EU PRIIPS the age ladder follows the regulation: 36+ months of operation → the full actual method; 6–36 months → the hybrid (actual costs over the highest multiple of six months operated, the standardised estimate for the remainder, blended by month weight); under 6 months → the NEW-PRIIPs estimate (expected annual turnover × a standardised per-asset-class cost rate over the portfolio composition). Two things matter in practice: (1) the fund's age is taken from the TcFundInceptionDate parameter — data alone is only a lower bound (partial history cannot be told apart from a young fund), so without a pinned inception date the fund stays on the actual method with a disclosure; pin the date to route a genuinely young fund. (2) The estimate's rule pack is configuration, not code: the TcSpreadCostTable parameter group holds the versioned per-asset-class rates (as decimal fractions), TcSpreadTableVersion selects the active version, and TcTurnoverEstimate holds the fund's expected annual turnover (whole-portfolio, or per asset class) — all maintained on the TC Parameters admin page and pinned in the snapshot like every other input. TcMethodologyForce overrides the tree per regime or per fund (recorded as forced); TcMinTradesForActual optionally routes thin-data funds to the estimate. A class with no rate or no turnover contributes nothing and says so — a placeholder, never a silent zero.
Every actual-method result also carries an asset-class breakdown (the CTI disclosure shape): costs decomposed by the trades' own asset classification, side by side with the portfolio composition. A class held but not traded in the window shows an explicit placeholder line (no number, with a finding saying so) — deliberately distinguishable from a calculated zero; trades with no classification group under "unclassified", counted and disclosed.
For both regimes, costs aggregate per rolling 12-month window ending at the as-of date, and the ratio is the summed cost over the summed per-window average AuM — computed from the snapshot's own pinned canonical AuM series, never a live table. A snapshot frozen before the required inputs joined the frozen set refuses to calculate with a "re-freeze" message (freeze again and the new snapshot pins them). Recalculating is allowed while DRAFT; after validation a new number means a new run.
- PRIIPs SRI (risk). The same rails carry the Summary Risk Indicator: a risk snapshot pins the fund's full canonical NAV history up to the period's as-of date, and the calculation then observes the regulation's own window: the past 5 years back from the as-of (RTS Annex II point 9) — a shorter series is used in full provided the frequency minimum is met (point 10), and the window is disclosed on every result beside the frequency, the share classes' recommended-holding-period/launch/lifecycle statics, the dividend series, the fund's canonically linked benchmark entities and their level series, and the configuration parameters. Calculate runs the regulation's own market-risk maths per share class — daily/weekly/monthly log-return volatility, skew and kurtosis → the 97.5% Cornish-Fisher VaR over the recommended holding period → VaR-equivalent volatility → the 1–7 market-risk class — with the observation frequency inferred from the series' own observed frequency and the regulation's history minimums enforced honestly (2 years of daily / 4 of weekly / 5 of monthly; a class is never fabricated from too little history — the share class shows no class and the finding says proxy/benchmark backfill is needed). Credit risk defaults to CR1 (a fund without a relevant obligor guarantee), always disclosed; pin the obligor's credit quality step in the
SriObligorCqsparameter (0–6, per your ECAI mapping) and the run maps it through the regulation's step-to-measure table instead — the SRI is the regulation's aggregation matrix over the two. The result shows the SRI headline with the per-class detail (SRI / MRM / CRM / VaR-equivalent volatility / annualised volatility / history / frequency) — and when the classes genuinely differ (hedged vs unhedged classes carry different volatility, so different MRMs and potentially different SRIs) the headline turns amber with the range and a flag says one number does not describe the fund: each class publishes its OWN indicator, the per-class table directly below is the authority, and the grid's Figure column shows the same range without any drill-in. Agreement is stated too (all N classes agree), so a single number reads as verified rather than assumed; an advisory SRI preview of the same maths is available before any freeze (see Series integrity below).
The same run also computes the performance scenarios (the amended regulation's historical-subinterval method): favourable, moderate and unfavourable from every overlapping subinterval of the recommended holding period's length across the last 10 years (RHP + 5 for longer holding periods) — best, median and worst evolution respectively, with the worst also considering the shorter recent subintervals the regulation prescribes (linearly scaled to comparable length) — plus the stress scenario from the stressed-volatility method (the highest-percentile rolling volatility fed through the regulation's extreme-percentile formula, never shown better than the unfavourable scenario). Values appear per holding period (after 1 year and at the recommended holding period; a third mid-point from 10-year RHPs) as amounts per 10,000 invested and average annual returns. The history requirement here is stricter than the SRI's (more than 10 years): a class with less shows no scenarios honestly — unless the fund's canonical model links a benchmark entity with a level series meeting the requirement, in which case the regulation's Case-2 supplement splices the benchmark's history before the class's own (always disclosed, including that the spliced portion carries no cost adjustment). With several linked benchmarks, pin SriScenarioBenchmark to select one — the engine never guesses. Scenario outcomes are net of the fund's pinned entry and exit charges (the same RiyEntryExitPct rates the costs-over-time block charges; ongoing costs are already in the NAV series) — every scenario value at every holding period scales accordingly, and where no rates are pinned the outcomes exclude those charges and a finding says to pin them. Scenario values live in the run's result and evidence (they are not published to the golden record — no Openfunds field identity exists for them in the current registry).
The published MRM follows its reference points. Every risk calculation records its month's market-risk class as a reference point; when a fresh calculation's instantaneous class differs from the latest recorded point, the published class follows the majority of the reference points over the preceding four months (the regulation's own damping rule — no strict majority falls to the latest point's class), the SRI is recomputed from the attributed class, and the class row shows an attributed · inst N chip naming both. A fund with no recorded points yet uses the instantaneous class and the run says recording begins with this calculation. Reference points are pinned into the frozen inputs like everything else, so attribution is reproducible.
- UK CCI risk score. The FCA's Consumer Composite Investments regime scores risk differently, and the platform computes it by the FCA's own book (the DISC sourcebook's fund path): the track record is weekly pricing (a daily canonical series is sampled to week-ends; monthly is the fallback) over 10 years ending no more than 60 days before the run's as-of date — a series that has gone staler than that boundary blocks the score rather than quietly computing from old prices (the regulation makes the boundary part of the track record's own definition), the volatility is the regulation's standard formula over non-overlapping simple returns, and the score comes from the 10-class annualised-volatility grid (below 0.5% = 1, up to 50%-and-above = 10). There is no credit-risk matrix and no scenario set — instead the regulation works with floors and adjustments, all applied and disclosed by the engine: an initial score of at least 9 where the track record is shorter than five years or pricing is less frequent than monthly (both derived from the series itself) or for the pinned product categories (
CciScoreFloor— CFDs, significant leverage); where an investor could lose more than they invest the engine assigns the regulation's terminal 10 itself from the classification — a mandatory final assignment no adjustment can move; a +1 for low liquidity (CciLowLiquidity, skipped when the score is already 9 or above — the rule's own carve-out); and the manufacturer's documented judgement adjustment (CciScoreAdjustment, e.g. −1 where the period contained extreme market anomalies) — the pinned parameter plus the run's append-only evidence are the record-keeping the regime requires. A short track record is supplemented from the canonically linked benchmark exactly like the scenario engine (disclosed per class). The pricing cascade is explicit: weekly is the default; a score computed on monthly pricing carries the recorded justification that weekly was not reasonably obtainable (CciWeeklyNotObtainable— the sources approached; vendor convenience or feed cost never qualify) or a warning asking for it; and pricing less frequent than monthly does not degrade into a thin volatility — it pre-sets the score at 9 or more, shown as its own terminal state on the result. The score lives in the run's result and evidence; nothing publishes to the golden record (no Openfunds field identity exists for the CCI score in the current registry).
- Validate → Approve → Release. Maker-checker with separation of duties: the maker cannot validate their own run, and the approver can be neither maker nor validator. A run with no calculated result cannot be validated — an empty number can never be released. Each step opens a dialog explaining the control and capturing an optional reason for the audit trail; a refused step (wrong state, separation of duties) shows the control's own message in red inside the dialog — the dialog stays open, the run stays where it was, and nothing is silently swallowed. Every transition is an append-only run event. The dialog is also findings-aware: when the run carries blocking findings it lists them in red, the reason flips from optional to required, and the ledger records the acceptance by name — validated over 2 blocking with your typed reason beside it. This is deliberately not a hard block (progressing one regime while another is short of data is legitimate work); it turns "a named person clicked past a known gap" from an embarrassment into a recorded, reasoned decision. The hard completeness gate stays where it always was — at document publication.
The fund page's lifecycle bar. The fund drill-in opens with ONE bar for the page's reporting context — Freeze → Calculate → Validate → Approve → Release. Exactly one step is ever the primary action; completed steps carry their record in words (who, when); locked steps say why they are locked. The bar drives the same runs and snapshots as the grid and the audit rows below it — one chain, one obvious next step — and the freeze can now start from the fund page itself.
How the page is organised. Assessments is where a production cycle starts; Operations is where you find work; Calculations is where you do it — the cockpit, exception cases and republish queues live on the Operations page; this screen keeps the work list and what serves it. A Context strip at the top sets the regime and the assessment as-of date everything below reads. There is deliberately no period picker and no quarter/window mode any more: you choose one date (it opens on the latest complete quarter end, never "today"), the cycle chips are labels that resolve to quarter-ends, and each regime reads its own window ending at that date — 36 months for PRIIPS and UK CCI transaction costs, 12 for MiFID, 3 for DCPT, while the risk regimes read their own histories. The strip says the derived window in words, because there is no single "regime period": one-off and ongoing costs read the preceding 12 months, transaction costs the preceding 36, and the risk score its 10-year track record — all from the same as-of date, each by its own rule. The Portfolio status grid is the working surface; the book-wide findings triage lives on its own Findings screen (sidebar → Regulatory → Findings — the Findings across the book → link underneath the grid lands there pre-scoped to the current regime and period; fund-level findings already render inline beside each figure in the catalogue); Deadlines & document reviews tracks the republication clock across the book (see The disclosure document workflow below); the Snapshots and Runs tables are collapsed audit views at the bottom (expand them for the cross-fund trail; each has a fund search and pages at 25/50 rows, and Runs has a status filter — status DRAFT is the checker's queue). Day to day you work the grid and the deadlines panel (with the Findings screen for cross-book triage), and rarely open the audit tables.
The calculation catalogue — every computed figure, by reporting context. Inside any fund's drill-in, the calculation catalogue panel (expand it) lays out everything the engine has computed for that fund, grouped by regime: the product classification (category, RHP, decision hash), then one section per regime with a run — for EU PRIIPS the full KID chain (the Annex II walk per share class: moments σ/skew/kurtosis → the 97.5% Cornish-Fisher VaR → VEV → MRM class, CRM with its source, SRI; the Annex IV performance scenarios per holding period in money-at-10,000 and annualised % with the stress scenario's own parameters — stressed volatility, percentile, window; the Annex VIII past-performance bars with honest gaps; and the costs side — components, buckets, methodology routing, costs-over-time / annual cost impact where computable), and the UK CCI / MiFID / DCPT sections in their own shapes. Every section carries its run evidence link (click → the full evidence pack; the run id itself sits on the pack's header as a copy chip — click it to copy the full id for checker instructions or a support ticket) and its frozen-input checksum chip, and where a figure is blocked the engine's own "why not" findings render inline — nothing is computed and hidden. Every cost result also carries "The working — how this figure was produced" (expand it): five rungs read top to bottom — scope (pinned trades → in-window count), pricing (the waterfall route distribution, with any zero-routed trades in red and the distinction spelled out: routed to zero because no arrival price resolved, not measured as zero — and why that can differ from the readiness page's price-coverage count), components (implicit + explicit − anti-dilution = direct, + indirect = total, with flooring named when it binds), denominator (the window-average AuM with per-year coverage) and ratio (the division, with the component percentages). The risk chain line leads with the same missing rung — how many observations entered and what window applied — before the σ → VaR → VEV → MRM → SRI walk.
The operations cockpit — what needs doing today. The panel above the grid reads the engine's own calendar and work ledger and answers the cold-landing question directly: each configured regulatory cycle (regime × due period) as a chip — fired ✓, due, or overdue Nd — the work queue by role (calculated runs awaiting a checker, validated runs awaiting an approver, approved runs awaiting release, document packs in review or blocked, narrative texts awaiting a second person, open republish decisions; each with clickable fund chips that jump straight to the item on its own scope, each naming its regime on the face — and the share class too where the work is class-level, e.g. a class's narrative chain, and a regime filter narrowing the run/pack/republish queues server-side — counts and samples both — because ops staff specialise: a costs specialist is never handed SRI items; narrative work is not regime-split and says so), and exception ageing (open cases, how many older than 7/30 days). When there is genuinely nothing to do it says so: "All clear — no cycles due, nothing waiting on a person, no open exceptions." No cycles configured? The panel points at the TcRegulatoryCycle parameter.
Open any fund — or any share class. The grid lists funds with activity for the window, but the "open any fund" search above it covers the whole known book — canonical fund entities by code or name, trade-feed codes, and share classes: an ISIN or class-name hit shows the class with its parent fund and opens the fund's page pre-filtered to that class (ops teams work the thing that has an ISIN, a KID and a client). A fund with only NAV/static data (a typical retail fund before its first calc) opens on its own fund page with the full disclosure workflow — classify, calculate, document — and joins the grid the moment its first run exists. The search also matches registered identifier codes: a canonical fund keyed by its name serves its registered fund code with the name attached (searching either finds it), and a fund with genuinely no name on record shows its code with an explicit "no name on record" label — a valid hit, not a broken row.
The portfolio status grid — running hundreds of funds at once. The grid shows every fund with activity for the chosen period and regime, paged at 25/50 rows: trades in the window with date coverage, the readiness verdict at freeze, the latest run's status and headline figure, its findings counts, and who has acted on it. It lands sorted by blocking findings, so page one is the actionable subset; re-sort by warnings / figure / trades / fund at any time. Filter by fund code — or an ISIN / share-class name: the filter matches the run's own classes, and a fund row expands in place (▸ N classes) to chips carrying each class's figure and blocking count, each opening the fund page filtered to that class. Filter by run status, or use the severity filter — Blocked (any blocking finding), Warnings (warnings, nothing blocking) or Clean (a run with neither), each pill showing its live count and matching the row's Findings pill exactly; click the active pill again to see all funds. Click any row to open the fund's own page (/costs/fund/…) — the whole workflow at full page width: a Share classes table first (one filterable row per class per regime — the share class is the thing with an ISIN, a KID and a client — each row with its own figure, findings and a ▸ inputs opener on the pinned rows), then the disclosure document pipeline (see the next section), classification, the calculation catalogue, and the snapshots & runs, with the same governed actions (verify drift, create a run, calculate, validate/approve/release, open the calculation detail). A share-class search hit or grid class-chip lands here with the class pre-filtered — and the filter scopes the work, not just the list: the calculation catalogue's per-class rows, the result panels and the evidence pack's class tables all narrow to the picked class (a banner states the scope — fund-level figures still cover the whole fund — with a one-click clear), and the filter lives in the URL so a filtered view is shareable and survives reload. The page is deep-linkable and carries its reporting scope, so a jump from the cockpit, the dashboard, the exceptions panel or the audit trail lands exactly on the item's own fund · regime · period; the browser's Back returns to the grid. The calculations read like a record, not a log: one card per reporting scope (regime × period) with the latest run on its face, named by id (run 9f3c21… — so "validate its draft run" always points at exactly one thing) and any frozen-but-unrun snapshot kept beside it; older runs and the full snapshot lineage sit one click away behind history · N earlier runs · M snapshots, and scopes other than the page's own context fold under earlier periods (N). Nothing is hidden — re-running a period is normal work (a data fix, a corrected parameter, a re-freeze), the audit trail keeps every run, and the evidence pack reaches each one by id. Under an EU PRIIPS or UK CCI context the grid also carries a Document column — where each fund's document pack stands (Draft / Blocked / In review / Approved / ✓ Released); other regimes show n/a because they produce cost figures, not a consumer document. The Findings column classifies each fund's latest run: red ⚠ N = blocking (the figure is incomplete or needs an action before release — e.g. no AuM denominator, re-freeze needed), amber N = warnings (data-quality signals worth a look), muted N adv = advisory disclosures (correct behaviour stated for the audit record — routes taken, conventions applied — never work items). Two batch actions keep the human work to exceptions and approvals only:
- Calculate (per row) runs freeze → open run → calculate in one action — the same governed steps as the individual buttons, same drift refusals and audit trail. A fund whose latest run is already Validated, Approved or Released is skipped with an explanation: a shortcut never silently supersedes work in review or a released figure (re-run deliberately from the run list when a republish is intended).
- Sweep: freeze + calculate all does the same across the whole grid as a background task (hundreds of funds complete in about a minute) and reports how many calculated, how many were left untouched in review, and how many failed. The DRAFT runs it opens are the checker's work-queue; re-running the sweep is the bulk retry — only failed or not-yet-run funds get fresh attempts. Approvals always stay one-at-a-time, deliberate decisions.
The disclosure document workflow — from calculations to a published KID or CCI summary. Open a fund's row under an EU PRIIPS or UK CCI context and the drill-in leads with the document workflow: a five-step journey rail — 1 Classify → 2 Calculate → 3 Assemble & check → 4 Review & approve → 5 Publish — that always shows where this fund stands and names the next step in plain words. Nothing on this path publishes on its own; every gate below is enforced by the platform, not by convention.
- Classify the product — the platform answers from its own data. Nine facts (can an investor lose more than they invested? how often is it priced? is the payoff a straight line? …) decide the product's regulatory category and the recommended holding period every cost-over-time figure is computed at. Classify from data derives each fact from what the platform already knows — the declared valuation-frequency field and the fund's own NAV frequency (using the risk engine's own frequency inference, so the two engines can never disagree), the declared legal form, the EMT/EPT structure markers (contingent liability, capital guarantee, options/leverage), the declared leverage and recommended-holding-period fields — and shows every answer with its source. Each fact has exactly one accountable owner: a derived fact traces to the data that produced it (wrong answer = wrong data — fix the source, never type over it; derived answers are visible but not editable), a pinned fact traces to the named person who set the
ClassificationFactparameter (the governed override, loadable in bulk through a normal parameters pipe), and an attested fact — the residue no data answers — traces to the person who supplied it in the review. When two data sources disagree (a declared "monthly" against a series that prices daily; share classes carrying different declared RHPs) the fact is a conflict and nothing records until the data is fixed or a pin decides it. An incomplete set is never recorded — it can never displace the active decision. Two conveniences keep the review honest but light: a fact another answer makes moot answers itself (answering Daily to pricing frequency auto-resolves the "monthly proxy" question — it only arises for products priced less often than monthly), and the review also carries the Liquidity section — nine further facts grounding the KID's early-exit disclosure ("How long should I hold it and can I take money out early?"). Five of them now derive from data the platform already holds, each shown with its source like every other derived fact: dealing frequency (from the pricing frequency), exit penalty (worst declared Maximum Redemption Fee across the share classes), notice period (from the declared subscription cut-off — a plain cut-off time reads as same-cycle dealing), secondary-market admission (a recorded exchange listing IS admission; absence stays unanswered, never assumed), and redemption-before-maturity (an open-ended fund — no maturity date — with active share classes redeems by construction; a fixed-term fund is never guessed). The remaining four — committed market making, alternative liquidity arrangements, discretionary exit prices, adverse-disposal impairment — are prospectus facts: upload the prospectus and extract them through document review (the approved extraction writes the fund's own data and the questions derive from it — extract once instead of typing four answers per fund; the questions carry a prospectus fact — extract via Documents link, and manual entry remains the fallback). Liquidity stays optional for the classification — a partial set is refused (it could never compute a class) and an unassessed status never blocks the classification or any calculation — but it does block KID release, so the KID preview will keep saying "liquidity not assessed" until the facts arrive. Classify book from data (next to the grid's Sweep) does this across every fund at once: complete derivations record, already-classified funds are left alone, and the rest report exactly which facts are missing. Two facts no platform data can answer — is the payoff a straight line? and are there unobservable valuation factors? — used to make every fund of the book a per-fund attestation. The Book policy button removes that at scale: a named person attests ONCE (versioned, audited, revocable) that funds matching the plain profile — no specified derivative, no leverage, daily pricing, each checked from the fund's own derived facts — carry the standard-category answers. Matching funds then classify with those two facts shown as book policy vN (the policy's version, attester and the profile evidence on the fact, exactly like every derived fact); funds that don't match keep asking per fund, and pins or real data always beat the policy. Re-attesting records a new version; revoking returns the book to per-fund attestation on the next derive — recorded decisions keep their provenance either way. Decisions stay permanent: Re-classify from data supersedes (kept on the audit trail) and automatically invalidates any approved document pack built on the old decision. - Calculate. The document binds two governed runs — the costs calculation and the risk calculation (for EU: PRIIPs SRI; for UK: the CCI risk score). The card shows what exists and, when one is missing, says exactly how to produce it (switch the context to that regime and Calculate from the grid).
- Assemble & check the document pack. Assemble document pack bundles the selected runs and the classification into one pack (the most recent approved run per role is pre-selected; the pack pins the exact runs it binds). Run checks walks its automatic states and stops honestly at Blocked — needs attention with the reason spelled out when something required is missing; fixing the input and re-checking is the normal path, and Acknowledge & proceed (with a written reason, recorded permanently) is the deliberate override.
- Review & approve. Send for review seals the pack — an integrity fingerprint over every pinned input. From then on the Integrity check button re-reads those inputs and answers plainly: inputs unchanged or the data changed underneath — the approval no longer stands. Approval follows the four-eyes rule: the platform refuses the same person who sent it for review, and the refusal message says so — that refusal is the control working, not an error.
- Publish. Release the pack, then publish each share class's document. Publishing is fail-closed: an incomplete document cannot go out. A product with no share-class entities (a mandate-shaped fund) says so here instead of showing an empty publish step — the KID is a per-share-class document, so load the fund's share classes (static-data pipe) and re-assemble, or treat the product as out of KID scope.
The drill-in's supporting cards complete the picture: The written sections is where the document's six human-authored texts (objectives, who it's for, how to complain, …) are drafted and approved — the author cannot approve their own text, and every save is a new version, never an overwrite. The document preview (available once the pack is sealed) opens the exact print-ready A4 document in a new tab; until the pack is complete and approved it carries a PREVIEW — NOT FOR DISTRIBUTION watermark and lists precisely what is still needed. Published documents is the registry of what actually went out — open any version, and withdraw the current one with a recorded reason (nothing is ever deleted; publishing a replacement supersedes the old version atomically, so there are never two live documents). Export published (CSV) (on the Calculations header) downloads the latest published data points for all live KIDs — one row per share class with the SRI, MRM/CRM, cost figures, RHP and liquidity class — for reconciliation against a master system; each row names its basis (published_snapshot for releases that stored their figures, or a recompose that is hash-verified against the published fingerprint and honestly labelled drifted when the inputs have moved since publication), and the export itself lands on the audit trail. The deadline puts the fund on the republication clock.
Deadlines & document reviews (the book-level panel). Every published document must be kept current, and this panel is that clock across the whole book. Coming due lists each tracked document with its next due date and owner (red = overdue, amber = within 30 days); reminders are recorded automatically at 90/60/30/7 days and a review opens when a document falls due (the clock runs with the daily cycle — Advance the clock now is the manual nudge). An open review is worked in two moves, both in plain words: Check the impact runs the deterministic comparison — has anything actually changed under the published document? — and then a person decides: No impact keeps the current document untouched (no cosmetic re-issue), Material routes the governed regeneration chain (recalculate → new pack → review → approve → publish; nothing republishes automatically), and Uncertain deliberately keeps the review open — the fail-closed default when nobody can yet say. A review raised as a concern is flagged urgent with a 5-day handling expectation.
Reading a run's evidence — the new result cards. The evidence drawer now renders the disclosure-grade result blocks as plain tables and charts alongside the figures it always showed: Costs over time (the KID's "what would an investment of 10,000 pay?" — total costs and the yearly impact on return at each holding period, with the growth assumption disclosed and the per-component make-up summing to the total exactly); Past performance (complete calendar-year bars only — a year without full data shows honestly as a gap, never a guessed bar); the UK £10,000 over time graph; and the UK risk route line stating in one sentence which FCA path produced the 1–10 score and why. Everything the cards show comes verbatim from the stored result — the raw JSON toggle remains the byte-level truth.
Findings across the book (sidebar → Regulatory → Findings). After a sweep you triage on the Findings screen instead of opening evidence drawers: every fund's latest run's findings are classified and grouped, so "912 funds are missing an AuM denominator" is one row, not 912 drawers. Blocking and warning groups show by default (advisory disclosure groups sit behind a toggle — they are statements for the audit record, not work). Expand a group for the fund list and click a fund to jump straight to its row and drill-in in the grid. One shared root cause across the book — say, a missing input feed — reads as exactly one line here. Before the first calculation for a window the panel says so explicitly ("no calculated runs yet") rather than disappearing.
Approve by exception — judging the whole batch at once. Approval is a fund-level act: every governed transition names one fund. But every signal worth judging on lives at share-class level, which is why reviewing a book one fund at a time scales badly and reviewing it by eye scales worse. The Approve by exception tab reads every share class in a reporting scope and decides about funds — splitting the batch into the funds a reviewer can accept as a set and the funds that need opening, each with the reason it is there.
A strip across the top plots every share class's risk measure on one axis with the batch median marked, so the shape of the book is visible before any number is read; classes far enough from the median to stand out are picked out in amber. Beneath it, Clear to approve as a set states what was checked rather than simply asserting safety — every class agrees with its siblings, sits clear of its own band's edges, has a normal return distribution and a full history, and carries no blocking findings — and Needs eyes lists the rest, blocking findings first, then the funds carrying the most signals. The signals are: share classes of one fund publishing different figures; a class sitting near a class boundary, where a small input move would republish a different class; a fat-tailed or skewed return distribution doing unusual work in the tail term; an attributed class differing from this month's instantaneous class, meaning smoothing changed the published answer; a short history; a class that is a peer outlier against the batch median; credit risk lifting the published indicator above what market risk alone would give; a figure or a performance scenario that moved since the fund's own previous calculation (the same two materiality tests the republication review applies to a released figure, asked here before approval); classes of one fund disagreeing on scenario coverage; and any blocking findings. Warning findings are noted on the fund's row but do not on their own send it to the attention list — they are worked on the Findings screen and as exception cases, and this screen exists to see what those cannot. A signal is a reason to look, never a verdict that a figure is wrong.
Two things keep it honest. The class boundaries come from the rules that set them — RTS Annex II point 2 for the SRI, DISC 5.4.1R for the CCI score — read from the same tables the engines used, so "near a boundary" can never drift from the boundary that actually decided the class. Everything else is a house heuristic, and the thresholds are printed at the foot of the screen so they can be argued with. Proximity to a boundary is measured relative to the width of the band the class sits in — the risk bands are wildly uneven, so a fixed number of points would flag most of a book at the wide end and nothing at the narrow end. The peer test also declines to judge where it cannot: a batch too small, or one where every class agrees, produces no outliers rather than a manufactured one. A fund is listed as clear only when nothing fires at all — the cost of a false alarm is one look, the cost of a false clear is an approved figure nobody read.
The cost regimes are read differently, because they publish differently. A cost run produces a ratio at fund level rather than a class per share class, so for those regimes the screen judges the ratio itself and the things that can hollow it out: no ratio produced, no average net assets to divide by, every trade routed to zero, no anti-dilution input found, and trades absent from the instrument master. The grain is stated on the screen so it is never ambiguous which is being read.
Conditions true of the whole book are reported once, not per fund. Where an input gap affects most of the batch — say, share classes with no computable performance scenario — it appears as a note on the batch head rather than a flag on every fund: a systemic gap repeated across nine hundred rows reads as nine hundred problems instead of one. What still flags per fund is unevenness — a fund whose classes disagree with each other from one frozen input set.
What this screen checks is on the screen. An expandable panel lists every test, split into those read from the rules — the same tables and materiality tests the calculation engines applied, so a check cannot drift from the rule that set the figure — and those that are the platform's own judgement, each stated with its threshold so it can be argued with and changed.
The screen ranks and explains; it does not transition anything. Approving, validating and releasing remain the per-fund governed acts with their separation of duties intact.
Exception cases — findings as owned work items. Every blocking or warning finding a calculation (or a freeze-time readiness check) produces also becomes a durable case: one per fund per distinct problem, with an age (how long it has been open), an optional owner, append-only notes, and a resolution history. Advisory disclosures never become cases — they are methodology statements, not work. The Exception cases panel groups open cases by root cause (the same grouping as the findings panel) — or, switched with the By fund toggle, pivots the same cases fund-first (one row per fund, its issues beneath, uncapped) for the reader who thinks in funds rather than causes. With a reporting scope selected the panel also surfaces the funds with the largest problem of all: never calculated — in scope for the window but never frozen or run, derived from scope rather than run output (no run means no findings means no cases, which is exactly how they used to stay invisible). The never-calculated cluster is marked derived from scope and has no assign/resolve — freezing and calculating those funds IS the resolution — so "what is not done?" is answerable from this panel alone. Each real cluster gets two actions: Assign cluster hands every open case in the group to an owner, and Resolve cluster closes them all in one action with a recorded resolution — accepted (a known, tolerated condition), fixed (root cause addressed) or won't fix — plus an optional note. The lifecycle is honest in both directions: a problem that stops appearing on a newer calculation auto-resolves as no longer observed (system-attributed, never because nobody recalculated), and a resolved problem that comes back reopens automatically with a note — nothing disappears silently. Every assign/resolve lands in the Audit Trail.
Three reading aids keep the panel honest at a glance. The reconciliation line compares the findings panel's actionable count for the selected window against the open cases tracked here (cases cover every period and regime, not just the selected one) — when findings outnumber cases it says why: findings sync into cases when their fund is calculated or frozen, so a book that has never been swept since the case layer arrived shows findings without cases until the next sweep. Each fund in an expanded cluster carries its case's own period and regime, and clicking it sets the reporting context to that scope before jumping to the fund — you always land on the grid that actually shows the problem. And a ↻ marker on a fund means input data (new or corrected trades in the window, or a newer ADL/expense sheet) arrived after the case was last observed — the ingested fix is sitting there waiting; recalculate the fund and the case re-evaluates (and auto-resolves if the data answered it). The marker is a hint, not a verdict — its absence never means a recalc is pointless.
Republish proposals — when a released figure's inputs change. A released number must not silently outlive its inputs. A daily scan (also on demand via Scan now on the panel) re-verifies every Released run's frozen snapshot against the current inputs; any drift — a corrected trade, a restated AuM value, a changed parameter — opens a republish proposal for that fund/period/regime (one open proposal per scope) and raises a bell alert saying what moved (e.g. "trades 13,480→13,502"). The scan only ever proposes: Accept — re-freeze & recalculate is the deliberate human click that re-freezes, opens the superseding run and calculates it — the new DRAFT lands in the checker's queue and walks maker-checker like any other run, and only its eventual release replaces the published figures. Clicking a proposal's fund (including right after accepting) sets the reporting context to the proposal's own period and regime before jumping to the fund's grid row. Dismiss (with a note) records that this observed change needs no republish — the same drift will not re-propose, but new drift on top will. The book stays honest in both directions: drift that disappears (an input reverted) auto-resolves its open proposal as stale on the next scan, and accepting is refused with a "re-scan" message if the scope has already moved on since the proposal was raised. The panel also proves the monitoring itself is alive: a permanent status line on its head reads Monitoring N released runs · last scanned Xh ago · M drifted — reassuring even when the proposal book is empty, because an empty book and a scanner that has quietly stopped are different things. It turns amber past 48 hours (check the daily schedule, or Scan now) and red if the scan has never run at all. For a released PRIIPs SRI run, observed drift also runs the review's own output tests: the scope is recomputed from today's data and compared against the released figures, and the proposal says which review trigger actually fires — a summary-risk-indicator class change, or the moderate scenario's annualised return moving by more than five percentage points — as red chips on the proposal row (and leading the bell alert), instead of a generic "inputs changed". A tested scope where neither trigger fires says so on the row — a green "Article 15(2)(b)/(c) tested — neither limb fired" chip, the platform's own answer to "why is no republish needed" — and a scope the tests could not assess is disclosed as exactly that, never passed off as clean. Regimes with no output materiality test defined (everything except PRIIPs SRI today) say that too — "input drift only" — so a blank never stands in for a verdict.
The validation library. Calculations also run a set of warn-first data validations over the frozen inputs — they never change a figure, they make you look: a per-trade ITC outlier threshold (the TcItcThreshold parameter — trades whose cost exceeds the configured fraction of trade value are counted, largest disclosed); a minor-unit (GBp vs GBP) mismatch check (a trade price ~100× away from its arrival quote is the classic pence-vs-pounds feed error that manufactures a 100× implicit cost — always on, disable per regime with TcMinorUnitCheck = off); a zero-route data-loss check (trades carrying raw quotes that still produced no implicit cost lost it to a missing side/price/units — "lost, not zero"); an AuM-conflict escalation (TcAumConflictThreshold — the always-disclosed MAX-collapse of disagreeing same-date AuM copies additionally warns when the disagreement is large enough to review before release); and a fair-value tolerance (TcFairValueTolerance — the pre-agreed exception threshold on each trade's price-vs-fair-value variance: trades outside it are counted with the worst variance disclosed, and trades already reviewed under an override treatment don't re-alarm). Their findings classify, roll up and become cases like every other finding.
Amending a snapshot — one class moved, prove nothing else did. When one share class's data is corrected after a freeze (a restated NAV series, a late dividend), a full re-freeze would absorb every change in the book silently. Amend… on a frozen snapshot derives a NEW snapshot instead: the named share class's rows are re-pinned from current data, every other pin is carried forward id-for-id, and the exact delta — which rows left, which arrived, for which class, why, requested by whom — is recorded on the new snapshot ("derive, never edit": the original stays immutable and superseded). The derive refuses when anything outside the declared class moved — another class's rows, or any fund-level input (trades, prices, FX, parameters) — naming what else changed; a re-freeze is the honest instrument for broader drift. The payoff is at approval: because untouched classes keep byte-identical pins and the engines are deterministic (both properties pinned in the test suite), their figures are provably unchanged — the checker reviews a diff ("SC-3 moved; the recorded delta is confined to SC-3"), not twelve figures on trust. The amended snapshot carries an amended · class chip, the run's calculation detail shows the Amendment section (scope, reason, delta, parent), and runs over it walk maker-checker exactly like any other. One caution the platform surfaces rather than hides: repeatedly amending an old snapshot leaves the untouched classes on ageing data — the republish drift scan reads that as drift like any other, so staying stale is a deliberate, visible choice.
Per-trade override treatments. When a review concludes on an individual trade — usually an ITC-threshold case — the answer is recorded as an override, one of two treatments: accept (the computed cost is genuinely right; the figure is untouched and that trade stops re-alarming the ITC check) or fixed fee (the computed implicit cost is wrong and a deliberate, non-negative fund-currency figure replaces it in the calculation). Overrides are configuration with the full reproducibility discipline: they are pinned into the snapshot like every other input, so a run applies exactly the set frozen with it, and adding or changing an override after a freeze shows up as drift — the signal to re-freeze and recalculate, never a silent figure move (an existing snapshot keeps calculating on its frozen set until you re-freeze). Every applied treatment is disclosed on the result ("N trade(s) carry override treatments…"), and creating an override can resolve the exception case it answers in the same action — the override is the case resolution, recorded once with the trade identity. Nothing is ever deleted; deactivation is soft and audited.
Series integrity. Alongside cost validations, the platform assesses the canonical NAV/dividend series themselves — the inputs a risk and performance engine will consume: history span vs the recommended holding period (short history means proxy backfill will be needed), NAV moves beyond 20% classified as spikes (the next observation reverts) or steps (the level persists — a restatement or a real regime change), dividends with no NAV within ±5 days, declared NAV frequency vs the series' own observed frequency, a first NAV that is late — or impossibly earlier — than the share-class launch date, and lifecycle-vs-activity disagreements (an active class gone quiet, or NAVs on a terminated one). Each share class gets a per-condition verdict; conditions whose inputs are absent say "not assessed" rather than failing — missing data is the completeness monitors' job. Advisory throughout: nothing blocks, everything is explained.
For API users, an SRI preview (GET /api/v1/risk/sri-preview) computes the PRIIPs Summary Risk Indicator from the same canonical NAV series — the regulation's own market-risk maths (return volatility → VaR-equivalent volatility → the 1–7 risk class), with the observation frequency inferred from the series' rhythm and the regulation's history minimums enforced honestly (a class is never fabricated from too little history). It is clearly labelled an advisory preview; the governed risk calculation — frozen inputs, maker-checker, release — is the PRIIPs SRI (risk) regime on the Regulatory Calculations page (see the workflow above), and both surfaces run the identical maths, so the preview is a faithful sight of what a governed run will conclude from today's series.
4. Calculation detail (the audit evidence pack). Buttons now say Calculation — the panel is a working view of the figure as much as an audit artefact, so it is named for what it shows. The drawer opens with the cost result named on its face: a Transaction-cost result title with the producing regime's badge and the methodology chip (actual / hybrid / estimated — hover for the full routing path; forced and age chips beside it where they apply) sitting directly above the headline percentage — no inferring "the first section must be TC" and no digging for the route. This matters because it is the point of the engine: five regimes (EU PRIIPS, UK CCI, MIFID ex-post/ex-ante, DCPT) each produce their own transaction-cost figure from the same frozen trade set under different windows, aggregation and routing rules — the face now says which one you are reading. Below it, a one-line plain-language explainer of what the figure is under the run's regime, and a Cost components table decomposing it — implicit (slippage), explicit (fees/taxes), any anti-dilution offset, the fund-of-fund look-through where it applies, an "of which direct" subtotal, and the total — each with its percentage and fund-currency amount (hover a percentage for the full stored precision). Below it: the methodology chip (which methodology ran, whether it was forced, how the fund's age was established — hover for the full rule path), the yearly-windows table with trades and average AuM per rolling year, a collapsible per-share-class table (each class's combined figure, plus the own/shared split when the share-class overlay applied), the estimated/hybrid component blocks where those methodologies ran, the cost-by-asset-class (CTI) breakdown with its placeholder markers, the price sources block (how many trades were costed via a supplied cost vs the mid/open/close quotes) with the supplied-vs-computed reconciliation chip, and the findings & disclosures list — alongside the frozen-input counts (labelled in plain language — and every count chip now opens the pinned rows themselves: the NAV series behind the scenarios, the expense rows behind ongoing charges, the trade set behind transaction costs, re-gathered under the snapshot's own rules and hash-verified so ✓ verified against the frozen checksum means the rows shown ARE byte-for-byte what the calculation read; drift is flagged and the rows honestly labelled current-state), the content checksum, readiness at freeze, the approvals block and the complete audit trail. Everything an auditor needs to reconstruct how the number was produced and who signed it off.
Past performance (EU PRIIPS risk runs). A PRIIPs SRI (risk) run also assembles each share class's Annex VIII past-performance bars: complete-calendar-year total returns in the class currency with distributions reinvested at the ex-date NAV (the gross amount where ingested, net as the disclosed fallback). The display follows the regulation: a ten-year frame (five where fewer than five complete years exist), blank bars for years the class wasn't live the whole year, never a partial current year, and the standard insufficient-history statement when no complete year exists. A distributing class's bar is never silently price-only — a year with dividend dates but no usable amounts shows blank with the reason (ingest the dividend amounts and re-freeze; snapshots frozen from spec v11 pin them). A benchmark draws alongside only under a PpBenchmark parameter naming the comparison benchmark for that fund — the scenario supplement's benchmark is never reused silently, because the two serve different regulatory purposes.
OTC and illiquid trades, and the enrichment work queue. The arrival-price waterfall prices each trade along a disclosed ladder: supplied per-route amounts → raw mid/open/close quotes → the bid/ask midpoint (OTC trades quoted both sides) → a single-sided quote (the available side as the best reference) → an independent fair value (the illiquid branch) → zero. Every route is counted on the run's price waterfall. A trade that still lands on the zero route opens an arrival-price enrichment case (GET /api/v1/costs/enrichment): the operational work item that gets the price found, tracking stages (security matching → reference → time series → intraday → open/close → independent price), attempts and candidate identifiers/prices. A case completes automatically when a later calculation prices the trade — re-ingest the market data and recalculate — and can be closed not possible with a recorded reason when pricing is genuinely unachievable; completed and not-possible cases stay searchable.
Costs over time and the summary cost indicator (EU PRIIPS). An EU PRIIPS run whose snapshot carries the v10 inputs also assembles the KID's costs-over-time table per share class: the Annual cost impact row — the KID template's own label; the methodology computes it as the reduction in yield (gross return minus the net-of-all-costs return) — at the regulation's presentation horizons — the recommended holding period alone when it is a year or less; one year plus the RHP in between; one year, half the RHP and the RHP for ten years and up — with total costs in money terms and a composition table (entry, exit, ongoing, transaction, incidental) whose rows always add up exactly to the total. The pieces come from governed inputs, each disclosed: the RHP and payment pattern from the fund's product classification; ongoing and incidental rates from the ingested expenses (with fund-holding look-through); the transaction rate is the run's own calculated figure per class; entry/exit rates from the RiyEntryExitPct parameter (absent means zero, said so on the run); and the growth assumption is the approved moderate performance scenario from the fund's approved/released PRIIPs risk run — pinned in the snapshot, so a newer approved scenario shows up as drift. A class with no approved scenario outcome is skipped with the reason (the prescribed 0% basis applies automatically when the RHP is a year or less); a fund with no classification, or a snapshot frozen before v10, gets a finding saying exactly what to do — the transaction-cost figure itself always still computes.
Every calculation runs under a registered rule pack. A rule pack is the versioned methodology package a run executes under — its legal sources, supported product types, required inputs, parameters and output contract, with an effective-date window. The pack that applied is recorded on every calculated run (visible in the evidence drawer's stored result under rule_pack), and release re-checks it: a run can only be released under an active pack. A draft pack can be calculated and reviewed — a shadow run — but never released; a retired pack means the methodology was superseded and the figure must be recalculated under its successor. Packs are immutable once active: a methodology change is always a new pack version with its own effective date, never an edit to the version your released figures cite. The registry is browsable at GET /api/v1/costs/rule-packs.
Product classification (API). For the PRIIPs/CCI disclosure perimeter, a governed product-classification decision routes each product before any KID-bound calculation: the PRIIPs category (1–4), the market-risk and scenario branch, the payment pattern (single investment / regular payment / notional / insurance-based / multi-option), and the recommended holding period as a governed fact with its source. The decision is made by evaluating the regulation's routing tests over typed facts you supply (POST /api/v1/costs/classifications) — every predicate, the fact it looked at and its conclusion are stored on the decision, so the category is never a free-text label. Missing facts block; nothing defaults: a decision over incomplete facts is stored blocked with each gap named, and a blocked product cannot open a calculation set. Re-classifying (say, a new RHP) supersedes the prior decision — which automatically invalidates any calculation-set approval built on it. The decision also carries a separate liquidity classification (liquid / limited liquidity / illiquid) computed from the liquidity facts — contractual redemption, secondary-market admission and market making, notice periods, exit penalties, dealing frequency, discretionary exit prices, adverse-disposal evidence — with the regulation's warnings attached; a declared profile is accepted as a disclosed fallback, and a product with no liquidity assessment is marked not assessed, which blocks KID release (never the numeric calculation — liquidity does not change the SRI).
Calculation sets (API) — one approvable unit per publication. A calculation set binds the component runs behind one disclosure — today the cost run and the risk run for a fund/period under a regime family (EU PRIIPS or UK CCI) — into a single lifecycle with a single binding hash. The set walks explicit states: DRAFT → SNAPSHOTTING → BLOCKED or READY → CALCULATING → CALCULATED → IN_REVIEW → APPROVED → RELEASED (with WITHDRAWN/SUPERSEDED as terminals). A set whose member snapshots failed readiness lands BLOCKED; an operator can acknowledge the exceptions with a recorded reason to move it to READY — the spec's "approved non-blocking exceptions". Submitting for review computes the binding hash over the classification decision, the regime's rule pack and every member's exact identity (snapshot checksum, methodology, rule pack, result). Approval signs that exact hash (with separation of duties — neither the maker nor the submitter can approve), and any change afterwards — a recalculated member, a re-frozen snapshot, a superseding run, a new classification — invalidates the approval: the approve/release simply refuses with the reason, and the set must be re-submitted. Release additionally requires the regime's set-level rule pack to be active; while a regime's disclosure perimeter is still being built its set pack stays draft, so sets shadow-run to APPROVED but cannot release. Endpoints live under /api/v1/costs/calc-sets.
Obligations and the review clock (API). Every disclosure duty is an explicit obligation (/api/v1/costs/obligations): the fund/share class, document type, due rule (annual/semiannual/quarterly/monthly), owner and the currently active release. The due clock rides the daily cycle run — a past-due obligation opens exactly one review case (replays fold in), and the approaching-due reminder ladder (90/60/30/7 days) sends a bell notification at each step — the 7-day step at warning severity — and records the step on the obligation; a step whose notification cannot be raised is retried on the next scan rather than silently marked done, and obligations whose due date cannot be read are counted and surfaced, never skipped. So no review is ever missed by memory. Event triggers (POST …/{id}/trigger) open reviews for product/data/model/market/concern/delivery events — a concern ("this may be misleading") gets an urgent days-level SLA, never queued behind the annual cycle. A review's impact assessment is deterministic: does the active release still match its calculation set's current binding? The materiality decision is fail-closed: material routes the governed regeneration chain (re-freeze → recalculate → new set → approval → release supersedes — nothing auto-publishes); no change records a confirmation and preserves the active document and hash (no cosmetic re-issue to reset the clock); uncertain keeps the review open and blocks straight-through. Straight-through no-change confirmation exists only under the pre-approved StpNoChangePolicy parameter with a deterministically clean delta, signed by the automation identity.
Publishing a document (API). POST /api/v1/costs/calc-sets/{id}/release-disclosure publishes one share class's KID or CCI product summary into the disclosure release registry — fail-closed: only a release-grade composition with a clean render can publish, and the refusal lists exactly what is missing. Each release stores the calc set and binding hash it composed from, the payload and rendered hashes, and the exact published bytes (GET /api/v1/costs/disclosure-releases/{id}/document serves them verbatim — the registry is self-contained evidence). Re-releasing supersedes the prior active version atomically — there is never a moment with two active versions or none — and withdrawal records the reason without deleting anything. POST …/{id}/dispatch records the hand-off (the deliver service transmits when configured — an unconfigured deliver URL is recorded honestly as not dispatched; the FCA UCITS/NURS package is recorded as handed off pending acknowledgement — direct FCA submission remains an explicit integration boundary).
Rendered documents (API). GET /api/v1/costs/calc-sets/{id}/kid-document?share_class=… and …/cci-document?share_class=… (both also accepting &language=…, defaulting to English) return the canonical rendered document — self-contained, A4-print-faithful HTML with the risk indicator, scenario and cost tables, the performance graph and the approved narratives, hashed (the rendered sha256 rides in a response header) so the same approved content always produces byte-identical output. A payload that is not release-grade renders with a PREVIEW — NOT FOR DISTRIBUTION watermark listing its blockers; a release-grade render carries none. Validation is deterministic: pending or draft narratives fail a release-grade render, and an estimated A4 page count (a disclosed content-budget model) rides with every render — exact page validation and the binary DOCX/PDF formats arrive with the rendering v2 step.
Length is judged per document type. The three-page limit is a PRIIPs rule and applies to the KID only — including a legacy KID kept in production during the CCI transitional period. A UK CCI product summary has no page limit: the standard is "short and concise" (DISC 3.1.1R) and understandable by retail investors (DISC 3.1.2R), which is an approver's judgement rather than a count. A long CCI summary therefore reports its estimated length as an advisory and is never blocked on it — blocking would risk pushing a manufacturer to cut content the comprehension duty requires.
Narratives (controlled content, API). The KID's and the product summary's narrative sections — objectives, target investor, complaints route and the rest — live in a controlled narrative store (/api/v1/costs/narratives): each save is a new draft version for its (fund, section, language) — share-class-specific overrides win over fund-wide text — and approval is a separate act under separation of duties (the author cannot approve their own text). The compositions fill their sections only from approved narratives; drafts show in previews with their status, and any section without an approved narrative is a release blocker — a document never publishes with a silently empty or unapproved section. Translations are language variants with their own version chains, and numeric values never live in narrative text (figures bind from the calculation set, so a translation can never alter a number).
Importing narratives in bulk. Clients arrive with these texts already written, so the Calculations header carries Import narratives: paste a sheet (straight from a spreadsheet — tab-separated, header row recognised; columns fund_code · slot · body · language · share_class), Preview shows every row's outcome without writing anything, and Import as drafts lands them. The controls survive the shortcut by construction: every imported row is a draft regardless of any status column in the file (approval stays a per-section, four-eyes act), a body identical to the current text is skipped (re-running the same sheet is harmless), an unknown section name is refused rather than stored, an over-length body lands with the renderer's refusal pre-announced, and one bad row never aborts the rest — the outcomes table sorts errors and warnings first.
Approving narratives across the estate. A book of fifty funds needs its written sections approved as well as imported, and approving them one at a time is not a control — it is an obstacle that invites shortcuts. The Calculations header therefore carries Approve pending narratives beside the import button: it approves every draft narrative for the working asset manager in one governed act. Nothing about the control is relaxed to achieve it. Each row passes through the same four-eyes gate as the per-section button, one row at a time, so a narrative you wrote is refused and listed as such — an author who wrote the whole book approves none of it — and each approval is recorded on its own narrative with your name and the time, because a batch reference is not an audit trail. The scope is always explicit (the estate in view, or a named set of funds), Preview shows exactly which narratives would be approved and which would be refused before anything is written, and already-approved text is never offered twice. The import door admits the product summary's registry sections too (cci.<FIELD_ID> slots such as cci.DISC5.MATERIAL_RISKS), because the zero-gap rule at Validate cannot be satisfied without them — a misspelt section name is still refused rather than stored, and a section whose value the platform calculates can never be filled with authored prose.
Working in more than one language. The written-sections card carries language chips — each language the fund has texts in, with its own approved count (en · 6/6, de · 3/6 — the selector is the per-language completeness report), plus + language to start a new one. The chosen language drives the whole document workflow: the card reads, saves and approves in it, the preview composes in it, the missing-text blocker names it ("no approved narrative in language 'de'"), and Publish releases it — each language is its own live document, superseding independently (publishing the German KID never touches the English one). Figures never vary by language; only the human-written text does. Published documents list a language chip on non-English versions.
KID content (EU PRIIPS, API). A submitted calculation set serves its structured Cat-2 KID payload at GET /api/v1/costs/calc-sets/{id}/kid-content: per share class, the Annex I section skeleton with every calculated variable bound from the exact reviewed members — the SRI and scenario table from the risk run, costs-over-time and the composition from the cost run, past performance, the RHP and the liquidity warnings — stamped with the set's binding hash. The payload says plainly whether it is release-grade (the set is approved, its binding still verifies, liquidity is assessed, nothing blocking) and lists every blocker otherwise; cross-member consistency (category, RHP) is checked at source. Narrative sections (objectives, target market, complaints…) are controlled content that joins at the document-rendering layer — each slot is an explicit placeholder here, never silently empty.
Data freshness at the document boundary (UK CCI). The 60-day freshness rule is anchored on the date a product summary is prepared or reviewed, not on the day a calculation ran — so the platform now re-checks it where the rule points: assembling a disclosure set, and releasing one, both refuse (in plain words, naming the share class and its dates) when the underlying risk track record ends more than 60 days before that moment. A calculation that was fresh when it ran can therefore still be refused at assembly weeks later — the cure is always the same and the refusal says it: re-freeze, recalculate from fresher data, and re-assemble. Runs calculated before this gate landed cannot be evaluated against it (their results carry no track-record end date); assembling from one attaches an advisory note instead of a refusal, and a re-freeze brings the fund onto the gate.
The EU incidental figure is a five-year average. The KID's performance-fee and carried-interest figure is computed as the average annual percentage over the last five years of fee history (the regulation's own basis), not the latest year's postings. Where fewer than five years exist, the observed years carry and the result says so — the regulation's estimate-and-backfill branch for young funds needs an approved comparable-fund assumption, which the engine never invents. Two smaller SRI corrections land with it: a fund whose prices are only available monthly now carries one additional market-risk class, as the regulation requires (the result's findings say when this applied), and a monthly-priced fund with five or more years of history is now correctly eligible for a score (previously the five-year observation window and the five-year minimum could refuse such funds at the boundary).
The working, for every engine. The catalogue's “The working — how this figure was produced” shelf now covers the risk engines as well as costs: per share class it walks the series that was read, the observed returns, the maths (Cornish-Fisher VaR → VEV for the EU indicator; the volatility band grid for the UK score), every floor and adjustment that applied, and the final score — composed from the stored result itself, so an auditor can check each rung by hand. Older results that predate some fields say so rather than guessing.
UK CCI risk routes and the performance graph. A UK CCI risk-score run now selects its DISC 5 route from the fund's product classification: the preset route (structured deposits score 1; CFDs, contingent convertibles, derivatives, VCT/EIS, significant leverage, possible loss beyond the investment, less-than-monthly pricing or a sub-five-year track record start at 9 or above — every predicate recorded, and a downward adjustment of a preset 9+ is refused); the structured route for non-linear payoffs (the ≥10,000-path discounted-worst-payout method — its maths is in place, and the route stays shadow-only until the payoff simulation engine lands, so such a run cannot be released); or the general volatility route as before. An unclassified fund routes general with a note. Every share class also carries the DISC 7 performance graph — a £10,000 hypothetical investment at each calendar-month end over up to ten years, distributions reinvested, with the regulation's warnings (past performance is not a guide; short history; a stale 60-day window) and honest omission of months that cannot be computed.
The complete UK cost table. A UK CCI run's result now carries the full DISC 6 completion: performance-fee and carried-interest blocks (the five-year average of actual postings over each year's average assets, with a £10,000 example — and honest states throughout: a feed with no performance postings is a genuine zero, no expense feed at all is missing, never coerced to zero; partial history is averaged over what exists and says so); a presentation table giving entry, exit, ongoing and transaction costs as percentages and as pounds per £10,000 (nearest pound, full precision kept); and a GBP conversion for non-GBP funds taken from a pinned FX observation with the rate, pair and date disclosed — no pinned observation means fund-currency presentation with a finding, never an invented rate. The IBIP biometric component is explicitly not applicable for funds.
UK CCI product summary and the public feed (API). A submitted UK CCI calculation set serves its DISC 4–7 payload and product summary at GET /api/v1/costs/calc-sets/{id}/cci-summary — per share class: the general product facts (DISC 4), the risk score with its route (DISC 5), the complete cost table (DISC 6, including the £-per-£10,000 presentation) and the performance graph (DISC 7) — stamped with the set's binding hash and carrying the same release-grade verdict as the EU KID content. ?format=csv returns the machine-readable CSV flattened from the same approved payload as the JSON (one row per share class per metric) — the free public-feed contract. Narrative sections are placeholders for the rendering layer, and the UCITS/NURS filing package is explicitly not assembled until the delivery layer takes the hand-off.
Cost by asset & sub-asset class. Every costs run's result carries the CTI-shaped decomposition — one row per traded asset class with its sub-asset classes nested beneath (the classification your trade feed already carries), portfolio-composition placeholders shown honestly, a classification coverage stat (share of windowed trades carrying a code), and unclassified trades counted with a finding. A window with no trades says so in place of the table. Trades in a currency the fund can't convert (no supplied conversion and no fund-currency figures) raise a finding that names the currencies — load FX rates for exactly those, or supply fund-currency figures on the feed. A ZERO_IMPLICIT_COST marker is disclosed as present, not interpreted wherever it appears — on the asset/sub-asset type codes or in the PRIIPs sub-asset-class column the client's own process populates — costs stay computed from the trade economics pending written confirmation of the marker's meaning. One note on timing: the sub-asset columns are pinned into the frozen inputs from spec v15 — a snapshot frozen earlier keeps its own rules, so re-freeze (or accept the republish proposal) after classifying trades to see the sub-class nesting on an existing run.
Released figures become platform data. The moment a run is Released, its per-share-class cost percentage is published to each share class in the golden record under the regulation's own Openfunds field (EU PRIIPS → EPT Portfolio Transaction Costs; UK CCI → its UK equivalent), as a decimal fraction with the period's as-of date and a citation naming the run. A released PRIIPs SRI (risk) run publishes the indicator triple per computable share class the same way — the SRI plus its market- and credit-risk components under the EPT fields (EPT Summary Risk Indicator / Market Risk Measure / Credit Risk Measure) and the EMT's PRIIP Summary Risk Indicator — so any EPT/EMT outbound pipe emits the governed risk figures with no extra configuration; a class the regulation refused (too little history) publishes nothing. That means the figure renders in the Fund Explorer with full provenance, and flows out through any EET/EMT/EPT outbound pipe that maps the field — no extra configuration: your calculated number simply fills the cost column the templates already carry. The publish is recorded on the run's audit trail (including the honest case where a run has no per-share-class figures yet because the fund's AuM-details feed hasn't been ingested). In the Explorer, a fund with cost activity shows a Regulatory costs panel — its latest run per period and regime with readiness, status, the headline figure, and a link to the Regulatory Calculations page. For external consumers — typically the document-production tool assembling KIDs, CCI product summaries or cost disclosures — the partner API's /v1/regulatory surface serves the released values (all regimes, costs and risk), the evidence pack behind each one, and a per-scope status that says whether a figure is current, whether a superseding run is in review, and whether input drift has raised a republish proposal. Only released figures exist on that surface — a draft or in-review value is never served — and supersession is explicit, so a consumer always knows whether it holds the current number or a replaced one.
Regulatory cycles — the calendar does the sweeping. Instead of remembering to run the quarter's sweep, pin the TcRegulatoryCycle parameter per regime (key1 = the regime, value = quarterly or annual; off disables) and the daily cycle runner fires the sweep automatically once each period boundary completes — in the regime's own period grammar (EU PRIIPS a 36-month window ending at the quarter- or year-end, MiFID ex-post/ex-ante 12 months, DCPT 3 months; UK CCI and the risk regimes the quarter itself). Each fired cycle is claimed (one firing per Asset Manager, regime and period — running the cycle check daily, or twice, never double-fires) and reports through a bell alert: how many funds calculated, how many were left untouched in review, how many failed. The DRAFT runs it opens are the checker's queue, exactly as if you had pressed Sweep yourself; approvals stay deliberate. UK CCI cycles carry the transition note (the regime becomes mandatory on 8 June 2027) on the plan and the alert.
6.1 Dashboard (Command Centre)
Live "mission control" across the whole platform.
- KPI tiles / gauges: Asset Managers, Share Classes, Files, Documents, Inbound Pipes, Outbound Pipes (each clickable through to its section). In the Fintech layout: AuM under assurance, data events, share classes, pipes live, files, deliveries, plus a trend chart with metric (Accuracy / Quality / Volume) and range (1W/1M/6M/1Y) toggles and an "Assurance health" card with three ring gauges (Reconciliation, Data quality, Pipes locked). The trend chart is built from a daily snapshot, so each metric grows one point per day. Quality and Volume are recorded for every asset manager. Accuracy is reconciliation accuracy, so it is only recorded for asset managers that have a destination read-back (PPC) source configured — for anyone else the Accuracy line is intentionally blank, and the fix is to configure a read-back source (see 5.6), not to wait longer. The global (all-managers) figure weights each manager by how many fields were compared, so a manager with 10 compared fields does not count the same as one with 10,000.
- Metrics strip: volume (data events, extractions), quality (discrepancies, quality score), operations (destinations, average onboarding time).
- Attention Required: clickable alert list; "All systems nominal ✓" when clear. Send Digest emails the platform summary to configured recipients.
- PPC Reconciliation and Delivery Health panels: accuracy vs the 97–99% target, per-destination "who to chase" bars, latest problem runs per outbound pipe.
- Agent: Pulse chat bar at the bottom ("Platform health summary", "Any errors or failures today?").
Regulatory Calculations on the dash (the cockpit rule). On either dashboard theme, with a working AM selected the Command Centre carries the Regulatory Calculations — operations instrument, reading one rollup — the same one the Operations page shows — so the dashboard, the cockpit and the bell never disagree. It draws the four role counts as the governance production line (Checker → Approver → Publisher → Pack approver, each stage in its role's colour with the first funds as jump chips; narratives and blocked packs fold under the Pack station; a mono "N waiting" beneath the line always equals the cockpit's queue total), closed by a fifth emerald Released station — figures published this period (quarter-to-date) — the line's throughput side: it counts cleared work, so it never joins the waiting total and never breaks the all-clear. The only two numbers with urgency semantics get the only two dials: the deadline countdown (days to the nearest registered obligation over a 30-day window — amber inside 14 days, red when anything is overdue, the overdue count on the sub-line, "no deadlines registered" when none) and the exceptions ring (segmented red blocking / amber warning, open-case count in the centre, ageing on the sub-line). Cycles sit on a quiet strip beneath (✓ fired / due / +N d overdue); whenever exception cases are open, an exception-SLA chip sits right-aligned on the same strip — green "exceptions within SLA" while every open case is under 7 days old, amber "N over 7 days" once cases age, red once any case passes 30 days — landing on the Operations Exception-cases tab. Stages land on Calculations with the run-status filter pre-set; dials land on their own Operations tab; at all-clear the line renders 0·0·0·0 as idle machinery — never hidden — and an overdue deadline breaks the all-clear by definition. The block also carries a Deadlines chip — nearest registered document deadline, amber when any fall due within 14 days, red when overdue — reading the same obligations list as the Operations page's Deadlines tab. The Attention panel folds in the same rollup lines ("Reg Calcs: N items waiting on a person"), and "All systems nominal" now genuinely means nothing — pipeline-operational and regulatory — needs a person.
6.2 Fund Explorer
Browse every entity's data with full source provenance — and correct it.
- Left panel: the entity tree for the selected AM (Umbrella → Fund → Share Class → sub-entities), searchable. Amber "ghost" parents mark structure that exists but isn't loaded yet.
- Product-type segments: when the AM's book mixes product types, pills above the list split it into Pooled funds (the default view), Segregated mandates and Internal pools (driven by the
investment_arrangement_typefield on each fund; funds without the field stay in the pooled view). Rows label funds by name (the code stays on the line beneath) and show the explicit ISIN where one is loaded. - Documents card (detail pane): every document that applies to the selected entity — its own, the ones a sibling represents it with, the ones that cover it, and the ones it inherits from its fund and umbrella — each labelled with how it applies, its languages/countries/date/version and a validity word (current, expiring, expired, no expiry). Type pills filter; latest only off shows version history. View streams a PDF into the viewer (other formats say download to open); Download saves it; Open in Documents → jumps to the store filtered to this entity. Amber No current KID / No current Prospectus chips appear when a document type marked required at this level (Documents → Document types; seeded: Prospectus per umbrella, PRIIPs KID per share class; an asset manager's level pin under Intake conventions moves the requirement with it) has no current document for the entity itself — a document inherited from the fund or umbrella never satisfies the entity's own requirement, and an expired one does not count. The card renders nothing for an entity with no documents and no requirement.
- Instruct to destination… (entity header): onboards the selected share class at a vendor through the destination's new-launch pack — pick a destination that carries a pack, preview the plan (already onboarded? how many file sets?), start. See 5.12.
- Language chips (detail drawer): when an entity carries multilingual fields (EET/EPT narratives in several languages), chips at the top of the drawer filter the display to one language (defaults to EN when present). Language-independent fields always show.
- Right panel: the selected entity's fields grouped into sections (Identifiers, Investment Policy, Pricing & NAV, Fees & Charges, Distribution…). Each field shows its value, a source badge (click to open the source PDF at the cited page, or download the source file), and a confidence %. Sub-entities (Portfolio Managers, Ratings, Registrations, Valuations…) render as their own boxes; NAV/dividend histories use a year → month drill-down so decades of data stay fast.
- Actions: edit pencil on any field for a manual override (optionally "remember for future runs"); a red "wrong level" chip offers to move a mis-placed field to the correct entity; Show Changes highlights conflicts (amber) and recent updates (blue); filter by source (documents vs pipeline).
- Quality Badge: per entity — completeness score, delivery-readiness status, and blocking reasons.
- Agent: Oracle — natural-language search; matching entities filter the tree.
The Explorer search matches names, ISINs, entity keys and registered identifier codes (the fund codes your trade feeds use) — and a matched fund brings its share classes with it, so searching a trade-feed code shows the whole product.
6.3 Golden Record
Ask Chronos for the policy-resolved truth of any entity. Type a question or click a suggestion chip; Chronos renders the golden-record table — field, value, source, confidence bar, effective date, and freshness — plus follow-up suggestions. Freshness: green "Confirmed Nh ago", amber within a month, red stale. Above the table, Delivered to closes the loop from value to vendor: each publication that carried this entity, with the delivered file itself one click away — exactly as sent. An entity that never left the platform says so honestly.
6.4 Time Travel
Ask Chronos point-in-time questions across the three axes (effective date × receipt time × source). Answers carry an axis chip (which axis was fixed) and a results table grouped by source with values, effective dates, and received-at times.
6.5 Upload
The bootstrap entry point for all new data (see 5.2). Select/create an AM, drag a file in, review the detection result (with an HITL header-row picker if needed), then Execute against a matched pipe, Build a new pipeline, or (per sheet, for Excel) Execute / Build / Ignore. PDFs and Word documents route to AI extraction instead. Arriving here from Quarantine pre-loads the held file.
6.6 Ingested Files (File Inventory)
Wide date-column sheets: a sheet shaped id columns × one column per date (FX rates, attribute grids) can be unpivoted in place — ⇄ unpivot date columns on the file row flips its analysed view to long form (…ids, date, value); build the pipe against that view and execution applies the same transform. Clear it by POSTing {"mode": null} to /files/{id}/reshape.
Transposed sheets: a sheet with field names down the rows and entities across the columns (or a two-column Field | Value fact card) can be transposed in place — when the platform recognises the shape, ⇄ transpose appears on the file row and opens a small dialog: confirm the layout and coordinates against a before/after preview of the header row, then apply. The analysed view flips to one row per entity, one column per field label, so the pipe builds (and locks its signature) against the labels — adding a fund later adds a row, not a column, and the signature stays stable. A sheet that does not support the declared coordinates is refused with the reason, never partially reshaped. Once a pipe is locked from a transposed file, later arrivals of the same shape (with more entities) are recognised automatically: the arrival is tried under the pipe's declared transpose and executes without any manual step — pipes locked before this feature need one re-lock to pick that up. Clear the mode the same way as the unpivot ({"mode": null}).
Long fact tables (field named in a value): some files carry one row per entity × field, naming the field in a value rather than a column header — a ratings register (…ISIN, Agency, Rating, Value, As of Date), a performance table (…ISIN, Measure, Period, value), or an XML value list after flattening (…ISIN, type, priceType, Value). A mapping binds one column to one field, so this shape cannot be mapped directly. When the platform recognises it, ⇄ pivot to columns appears on the file row and opens a dialog where you assign each column a role: id columns group rows into one output row (declare every code column — a row is refused only if all its id cells are empty); each distinct key combination becomes a column named by joining its values (Morningstar_Stars, CUMULATIVE_1_month); the value column's cell lands under it; carry columns ride along and must be constant per id (a conflict refuses with both values); per-key columns emit one column per key (CUMULATIVE_1_month_Start_date — use these for as-of dates or benchmark values that differ per key). Undeclared columns are dropped. Two rows claiming the same id + key combination refuse the reshape with both row numbers — a tabular file has no restatement convention to arbitrate, so fix it by adding a distinguishing column to the ids or keys. Entities missing a key simply leave those cells blank. As with the transpose, the pipe builds and locks against the pivoted view, a growing file adds rows not columns, later arrivals of the same shape are recognised and executed automatically under the declared pivot — and a new key combination in an arrival is a new column, so the file is held for review rather than the new field being silently dropped. The pivot also works on XML files (it applies to the flattened table, after the row-entity choice). Clear the mode the same way ({"mode": null}).
Zip deliveries: archives are checked for decompression bombs — an entry that expands past the platform's size limit is refused when you extract it, naming the entry and the limit, rather than being allowed to exhaust the service. A .zip upload (including zips of zips) is stored as a container — its row shows "N inner files"; expand and click Extract → per entry to mint each inner file as a first-class ingested file. Extracted files carry a "⧉ zip" source chip and a "derived from" chip pointing back to the archive (and the entry path), so provenance never dead-ends at an anonymous intermediate file. Content-hash dedupe applies to extracted files exactly like uploads. Operator-shaped re-uploads can declare the same lineage via the upload API (derived_from_file_id + derivation_note).
Every file the platform has received.
- Stat cards: totals, today/this week, ingested vs analyzed, AM count. Format breakdown badges.
- Table: filename (with a channel icon — upload ↑ / email ✉ / SFTP ⇄ / API ⌘ — and who sent it), AM, format, status, rows, size, received time. Running files show live progress; ingested files show their run reference.
- Actions: search/filter; Ingest an analyzed file (matches and executes its pipe — a generic pipe prompts for the AM); per-sheet actions on multi-sheet files; download the original (refused for a file quarantined by the malware scanner — see 5.2, "Macros and malware").
- Agent: Argus — "Show me all files for Meridian", "Are there any duplicate filenames?"
6.7 Delivered Files
Every generated outbound file. Stat cards (Completed / With Errors / Failed are clickable filters), a table with run ref, filename, pipe, destination, status + delivery-status chips, and rows. Per row: Download, Window bundle (every stored output that run's publication window produced that day, zipped with a manifest — a single-file window still bundles predictably, and anything unreadable is named in the manifest rather than silently dropped; the bundle holds only outputs executed for that window's Asset Manager — on a shared feed, another tenant's run of the same pipe to the same destination never appears in it, and the Publications board likewise attributes a run to your window only when it was executed for your Asset Manager), Re-generate, expand the error log. The listing shows the runs executed for your Asset Managers — a shared generic template's runs appear only to the AM each run was executed for, and Download / Re-generate check that same per-run ownership (administrators see everything, including legacy runs from before run-level ownership was recorded). With an Asset Manager selected in the AM picker the server returns that Asset Manager's runs alone; a legacy run recorded without an owner belongs to no selected Asset Manager and appears only under "All asset managers", to administrators. Agent: Hermes — "Any failed deliveries?"
6.8 Documents
The canonical document store (see 5.7). Stat cards (Documents, Types in use, Expiring ≤30d, Expired, Supersession review), filters, and the main table (type, filename, languages, countries, version, validity, visibility, AM). Row click opens the detail drawer: metadata editing, entity links, permalink copy/rotate, version history, download. Side panels: Validity attention and Supersession review (Merge / Keep separate). Header buttons: Upload document and Intake conventions. Agent: Argus — "Which documents expire this quarter?" Argus answers about the documents on this page from the canonical store (types, versions, what expires when, extraction queue) — the scraped inventory is secondary. Document types (header button) is the vocabulary itself: for each type its level, whether every entity at that level must hold a current one (Required), the expiring lead in days, the validity rule, the default visibility and the platform default extraction template — editable by a platform administrator, read-only for everyone else. Documents the web scrapers collected before the canonical store existed are brought in by an operator with scripts/backfill_scrape_documents.py (dry-run first; a category the mapping does not know is reported, never guessed). Two more header buttons since WS-B: Manifest pipes (the metadata-file shapes that register their companions automatically — see 5.7) and Upload manifest drop (a manifest and every document it names, as one arrival). A document's drawer shows Metadata sources — which field came from a manifest column, an embedded property or the filename convention.
6.9 Data Quality
Discrepancy intelligence. Stat cards (Total / Open / Resolved / Material), severity and status breakdowns, the AI Triage Report generator (pick AM + severity → a full clustered analysis with recommended actions), and one overdue grid per completeness monitor (Overdue NAVs, Overdue Distributions — entity, last date, expected frequency, days overdue; ≥7 days shows red). Document completeness grids sit above them — one per document type marked required at this level: Share classes without a current PRIIPS KID, Umbrellas without a current Prospectus — listing every entity in scope that holds no current document of that type (missing, amber) and every one whose only current document runs out within the type's lead (expiring in N days, with the filename and the date). The rule is the Explorer chip's rule: a document counts only when it is the entity's own, a document inherited from the fund or umbrella never satisfies the requirement, an expired one does not count, and a share class whose lifecycle is terminated or in liquidation is out of scope. The daily scan raises one bell per asset manager per type — 2 share classes without a current PRIIPS KID — that collapses on repeat and clears itself on the first scan that finds the book complete. Which types are required, and the lead in days, are properties of the document type — Documents → Document types (a platform administrator edits them; everyone else reads them; seeded: Prospectus per umbrella, PRIIPS KID per share class). When an asset manager's intake conventions pin a type to another level (a Prospectus attached per fund), the requirement follows the pin: that manager's funds are asked for one and its umbrellas are not — the grid and the Explorer chip agree with where registration attaches the document. Agent: Argus/Sentry — "What are the most common field mismatches?", "Which share classes have no KID?"
Cross-document consistency. A value approved from a document (see 5.7) is one source like any feed: two versions of a KID that disagree on a share class's ongoing charges, or a document that disagrees with a data feed, become one discrepancy row naming both sources — a document side reads Document: KID — kid_v2.pdf (p.3), a feed side Pipeline: Daily NAVs. Detection runs nightly (and on Detect); the triage table's Source kind filter narrows to document vs document, document vs feed or feed vs feed, and document sides render as blue chips beside the violet feed chips. The AI triage report cites the document filename and page for every example it names, so the steward can open the page it read.
6.10 Lookup Review
The HITL queue for unknown values (see 5.5 step 3). Grouped by lookup table; each item shows the raw value, occurrence count, and the AI proposal with confidence and reasoning (clickable alternatives). Confirm / Confirm Override / Reject, with a scope picker (pipeline / AM; global appears for platform admins only — a global entry applies to every Asset Manager's data). Enum-targeted overrides are constrained to a dropdown of valid values. When the autonomy policy clears items itself, it never promotes at global scope: an item that would land globally is parked for an administrator.
How a confirmed entry is found at run time. Resolution walks your own pipeline-scoped entries first, then your AM-wide entries, then the template's own entries (a lookup fix a platform administrator made on a shared generic template, which every tenant running that template reads), then global — and stops there. Your own statements about your own data always beat a platform default: a spelling you confirmed AM-wide wins over the same spelling fixed by an administrator on the template. A pipeline-scoped confirmation is yours even on a shared template: another Asset Manager running the same template never loads it, and their unknown values still land in their queue for their reviewers. An unknown value is never answered from another lookup table or from another tenant's entries; it is proposed here for a person to decide. The lookup chip on a mapping (the conversions-in-effect view on the pipe page) shows exactly the same picture in the same order — global, the template's own entries, and your Asset Managers' entries — and never another tenant's overrides, even on a shared template; if you hold several Asset Managers, choosing one narrows the view to that tenant's entries plus the platform ones.
Platform items. An item with no Asset Manager is a platform item — it is raised when a platform administrator previews a generic template without choosing an Asset Manager. Every tenant can see it in the queue, but only a platform administrator can confirm or reject it — for anyone else the row shows a Platform item note in place of the Confirm and Reject buttons: it defaults to global scope, because a spelling fixed on a generic template is a fact about the spelling, not about that template — confirming it at global scope writes the global vocabulary, and confirming it at pipeline scope writes the template's own entries instead (which every tenant running that template reads; use it only for a code list specific to that format). The scope you pick is always checked against where the entry would actually land, so an item that carries no pipeline cannot be confirmed at pipeline scope and an item with no Asset Manager cannot be confirmed at AM scope — you are told which scope to choose instead, and nothing is written.
6.11 PPC Reconciliation
Destination read-back reconciliation (see 5.6). Accuracy hero vs the 97–99% target with per-destination bars; SLA card; diff stat cards; "who to chase" breakdowns; the diff table with per-row Accept Golden / Accept PPC / Manual Override; and the Compute diffs button (needs an AM selected). Diff-type colours: value changed = amber, missing at destination = red, extra at destination = violet.
6.12 Inbound Pipes (list)
All inbound pipelines. Clickable stat cards (Total / Locked / Review / Avg Confidence / Failed / Recent Runs), a searchable pipe browser (name, client, status, mapped columns, confidence), and Attention Required + Recent Runs panels. Agent: Argus — "Which pipelines have low confidence?"
6.13 Inbound Pipe detail
The full working surface for one pipe (see 5.2). Key areas:
- Header: status chip; AM binding (or "Generic template" with Bind to AM; Make generic template is shown to platform admins only); Mark as PPC; Compare Mappings (diff vs the previous lock); Lock / Unlock; Rebuild (re-runs AI mapping — confirm required. A pipe built from one sheet of a multi-sheet workbook rebuilds against its own sheet's current columns — never the workbook's first sheet — keeps its "(sheet)" name suffix, and is refused, naming the sheet, if that sheet is no longer in the file. Bounded-block pipes cannot be rebuilt — delete the pipe and re-declare the block from the file); Delete.
- Entity model card: "What does each row represent?" — entity type, key column, parent, date/currency columns, composite-key builder with Preview. Read-only while locked ("Unlock to edit"). Static pipes also offer Key resolution — Via identifier registry declares that the key column carries a client CODE, resolved through registered identifiers to the canonical entity at execution (the platform-native join for enrichment feeds); an unresolved code fails that row with a pointer to register the code field. For static pipes (share class / fund / umbrella) an Attach-only checkbox declares an enrichment pipe: rows whose key matches no existing entity are skipped with a run finding ("N rows would have created new entities", with sample keys) instead of silently minting bare stubs into the Explorer. Use it for any load whose job is adding facts to an already-loaded book; leave it off for the pipe that builds the book.
- Row layout card: which source rows this pipe reads, in 0-based source coordinates — the header row, data starts at, ignored rows, evidence rows (see 5.2) and the bounded-block fields (last data row, first/last column — see "Bounded data blocks" in 5.10). This is the door for files that arrived by SFTP, email or API, which nobody saw on the Upload screen: change the layout here, header row included, and Save layout & rebuild re-runs Atlas against it (your confirmed sub-entity groups stay); every run then applies the layout, whichever way the next file arrives — the pipe is the source of truth, not the ingest record. Anything below the header needs the header row pinned; a row at or above the header is refused naming the cure. Locked pipes are read-only here: unlock, save (the pipe rebuilds), review, lock again — a locked pipe's signature and mappings are what arrivals are matched and run against, and a layout change re-derives both. A file whose auto-detected header row differs from the row a locked pipe declares still matches: the arrival is re-read at the pipe's header row before it is quarantined.
- Sub-entity groups / decomposition / batch sequencing panels: confirm multi-value column groups; split multi-entity rows; make this pipe wait for sibling pipes in the same delivery batch. Full walkthroughs in 5.10 Advanced pipe shapes.
- Mappings table: source column + samples, OF target (clickable spec), confidence, normalisation summary, Reviewed checkbox, Skip toggle. Row click opens the Mapping Inspector: source samples with enum flags, target spec, transformation in plain English, live preview with per-row Fix pickers, AI reasoning + alternatives, and the Edit section (rule builder, Ask Atlas, Skip, Mark reviewed).
- Agent chat (Atlas): natural-language mapping edits; disabled while locked.
- Execute (locked pipes only): pick file (+ AM for generic templates); Preview = dry run; Execute Pipeline.
- Run History: live progress for running runs; Revert / Restore per run (the golden-record recompute a revert or restore owes is queued as a recorded background task whose id the response names — a failure to reach the Kairo engine is recorded on that task, never silently dropped, and approving a structural conflict queues the same recompute for its entity); "completed with errors" expands to grouped row errors with inline Fix (lookup) and Fix date actions. The history lists the runs executed for your Asset Managers only — on a shared generic template, another tenant's runs of the same pipe (their filenames, error logs and findings) never appear, exactly as on the Delivered page; platform administrators see every run. Runs also carry data-quality findings — column-level checks that run on every ingest (e.g. "every value in this column is LEI-shaped; the source column is likely misaligned", impossible NAV/AuM values, a mapping failing on ~100% of rows, a date column carrying Excel serial day-numbers — which now convert automatically, with an info finding noting the conversion). Findings never block the ingest: the values load, the finding shows on the run (amber = warning), and warnings also raise a bell notification. Run arithmetic always adds up: succeeded + failed + excluded + never-attempted = input rows (excluded = rows a declared row-exclusion rule removed, counted per rule — see Row exclusions in section 5.9). When a run stops early at the error cap (100+ row errors), the abandoned remainder is counted, the error log leads with "RUN STOPPED EARLY: N of M rows were never attempted", and the run row shows an amber "N rows never attempted" note — fix the errors and re-execute; that data is not in the platform. Fix the source file or the mapping, then re-execute — or Revert the run. One finding carries a one-click action: when a custom field column looks like an identifier (a fund-code column, say), the finding offers Register as identifiers — codes in custom fields are display-only until registered, after which code-based matching (calc linkage, fund resolution) can use them. Executes run on the platform's background worker where one is deployed, so a web-tier deploy no longer interrupts a run in flight; and an interruption that does happen (the worker itself replaced mid-run) is never left hanging: the stall watchdog closes any run with no progress for 10 minutes as failed, with that reason in the error log, and rings the bell exactly like any other failed run. Nothing partial is kept — a run's values commit only when it completes, so an interrupted run simply re-runs whole, and re-running is safe for the same confirmation reason as above. The run's background task record is closed with the same honesty, so Execute works again immediately rather than reporting a phantom "already running". If an execute sits queued for an hour without starting, the platform closes it and raises a bell naming the pipe — the usual cause is the pipeline worker not running; execute again once it is.
- Publications affected: the platform computes which outbound publications depend on this pipe — the delivery subscriptions whose scope covers entities the pipe has loaded, with the affected share-class count per publication. Delta-scoped subscriptions report potential coverage (the entities eligible under their filters), never a precise figure, because a delta send list is only fixed at the moment the window fires.
- Delete now cascade-reverts: deleting a pipe removes its ingested data events and recomputes the golden record from the surviving sources, so a deleted pipe's values never linger as live data. (Locked pipes still refuse deletion — unlock first.)
6.14 Outbound Pipes (list)
Delivery-format pipes grouped by destination. Stat cards, searchable browser, Attention/Recent panels, and the Build Pipe form (destination + optional AM + template upload + "Preserve template format" for Bloomberg/Morningstar/BDUP workbooks). Agent: Hermes — can take actions from chat ("Execute NAV feed").
Bundle pipes. The Bundle Pipe button creates a different kind of pipe: one that delivers released UK CCI disclosure bundles — the product summary (HTML and PDF) and the machine files (Kairo CSV v1 and JSON) — as sealed artefacts. There is no template and there are no column mappings: content comes from the release itself and is hash-verified against the release's own manifest before every send, so what a destination receives is byte-identical to what compliance released — a mismatch fails the run with nothing sent. Bundle pipes carry a violet bundle chip in the pipe table. Scope — which funds, classes, languages and artefact roles, and full vs delta — lives on the subscription, exactly as for any other pipe; a delta subscription only sends bundles the destination has not already received at that version (tracked by delivery receipts). A bundle run's receipts describe the zip as sent — the receipt's hash and size are of the transmitted file, so the counterparty can verify what landed against it; the disclosure set's own sealed hash stays on the release and inside the zip's manifest. When a disclosure set is released, subscriptions with an "on new data" trigger fire automatically.
Document pipes. The Document Pipe button creates the third kind: one that delivers the Asset Manager's canonical documents (KIDs, prospectuses, reports — see 5.7) rather than data. Pick a destination and what leaves — the documents as raw files (one file per document over SFTP or a REST API; one zip by email) or a metadata file (one CSV of type / languages / countries / entity / dates / version, with a permalink for each public document). No template, no mappings; the pipe carries a sky documents chip. Which documents, for which entities, and full vs delta live on the subscription's document rule; every document version that leaves is receipted per destination, so a delta window sends only what the destination has not already received. The gates at send — destination-country registration, validity, the CCI identity check, first production check — are recorded on the run, and a run that sends nothing says why.
Pack pipes. A fourth kind is never created from this page: saving a destination's new-launch pack (Delivery → Pack) creates the destination's pack pipe, marked with a fuchsia pack chip. Its runs are the file sets of an instruction — the Insert form, the NAV history and the latest documents for as many share classes as the destination's file budget allows, suffixed _01, _02… It has no mappings (the Insert form renders through its own mapped pipe), cannot be executed by hand and cannot be subscribed; start an instruction on the destination or from a share class in the Explorer. See 5.12.
6.15 Outbound Pipe detail
One delivery pipe. Editable name; status; Preserve format badge for binary templates (with workbook info); Rebuild / Lock / Unlock / Delete. Panels: Egress validation rules (the mandatory/conditional/value-check baseline, expandable); Mappings table (destination column with required/type/enum chips, Openfunds source, confidence, inline Default values, Review/Skip); Delivery (points you to Subscriptions for scope + schedule); Output file naming (pattern with clickable tokens like {client}, {date}, {increment}, the run stamp's parts {year} {month} {day} {hour} {minute} {second}, {scope} — the run's cut, Full or Delta, taken from the subscription that triggered it — {client_id}, the Asset Manager's numeric id, and {asset_manager_identifier}, the client's identifier code from its Asset Manager record — a pattern that names it refuses the run until an administrator has set the code, rather than delivering a blank in the outlet's filename); a pattern is checked when you save it — from the page, from Hermes, or from the outlet-pack importer alike: only the listed tokens, no / or \ or .. (the destination's remote path decides the directory), and a refusal names the token or character and the accepted list, so a pattern that would silently deliver under a default name never persists; an older pattern the platform cannot render fails the run naming it rather than delivering under a default name, and a value a token carries — a client name with a / in it, say — is flattened to _ before it becomes a filename); Execute (locked only — pick AM, Generate); Run History (download/re-generate, validation findings with a "Fix with Hermes" hook, "N not ready" readiness badge, and the Enforce readiness toggle). Agent: Hermes HITL chat (disabled while locked).
A bundle pipe's detail page replaces the mappings table and HITL chat with a card explaining what feeds it (sealed released bundles, hash-verified; scope on the subscription); Delivery, Output file naming, Execute and Run History work as for any pipe. A bundle run's output is one zip per window — a folder per fund/class/language/version plus a manifest.json naming every artefact's fingerprint and size. Every run row also carries a Receipts drawer — the delivery-evidence record per transmitted item: for bundle runs one line per released bundle (with its version), for classic runs the output file; each line shows the channel, the status ladder (stored → sent → delivered → acknowledged, with preview/failed/blocked as the honest exceptions), the transport facts (SFTP path and byte count, HTTP status, email Message-ID and recipients), the timestamp, and the receiver's acknowledgement once captured. This is the "which distributor received which version, and when" answer, on the run itself.
6.16 Delivery
Destination management console (also reachable in Admin mode — see 7.1). Stat cards, the destinations table (adapter badges, targets, schedule, status), breakdown/attention/recent panels, + New destination, per-row Edit / Schedule / Pack (the destination's new-launch pack and its instructions — see 5.12 and 7.1) / Delete, and the full Hermes agent (query and act — Hermes can also start a new-launch instruction for you; it always shows the plan first and asks you to confirm).
6.17 Audit Trail
Everything the platform did — who triggered it, when, the outcome, and the AI cost. Stat cards (Events, AI Cost, Errors, Actors, Inbound Files, Scrapes); filters by event type (Pipeline Run, Inbound File, AI Call, Chat, Drift Alert, Delivery, Scrape, Admin, Reg Calc), status, actor, and date range; Export CSV / JSON (respects your filters, up to 5,000 rows). Rows expand to full detail; an event with nothing further to show says "No further detail recorded" instead of an empty panel. With a working AM selected, the trail also merges the regulatory-calculations governance ledger — every run calculate/validate/approve/release and document-pack transition with its actor and reason — so the maker-checker history auditors ask for first is on the main trail, not only on each pack's own history view. Reg Calc rows carry their substance on the face: the second line shows the pack's state transition (IN_REVIEW → APPROVED) and/or the recorded reason (what was published, why a freeze refused), so three same-second events read as a narrative rather than identical labels; long ids in stored reasons display shortened. Each row's expansion offers Open — fund drill-in, landing on the event's own fund, regime and period, where the run (named by id) and its evidence pack are one click away. On narrow windows the page opens on the Activity list (Sentinel's chat is the tab beside it). Remember: selecting a specific AM hides platform-level admin events — choose "All" to see those. Agent: Sentinel — "What failed today?"
6.18 First Production Check
The reviewer queue for brand-new share classes (see 5.5 step 4). Grouped by AM; each item links to the Explorer for inspection; Approve or Reject (comment required to reject).
6.19 Structural Conflicts
Held structural changes (see 5.5 step 5): each item shows the attribute (parent link or identifier), the current → proposed comparison, and Approve (apply) / Reject (keep existing). A Currency Topic item (see 5.10a) is a share class arriving in a currency it has never published in: the existing side lists the class's confirmed currencies, the proposed side the new one; Approve confirms the Topic (rows in that currency write from the next run), Reject marks it rejected until an operator lifts it.
6.20 Document Review
HITL review of an AI document extraction (see 5.7): extracted fields with confidence bars and page citations, the Doc Review Agent chat, and Approve & Write / Reject. The Entity tile says where an approval writes — from the document's links (the linked share classes / fund / umbrella; a disagreeing model key is shown as a finding) or read by the model (no links: tick Confirm entity … before Approve is enabled). Approve reports the entity keys the values were written against.
6.21 Publication Board
The forward view of the delivery day — one row per destination × publication window, derived from your delivery subscriptions. Nothing here is a new source of truth: delivery status comes from the runs, the calendar from each subscription's own schedule (or its destination's).
- Row anatomy: destination + asset manager, publication (the outbound pipe), today's window(s), cut-off with a live countdown, the inbound feeds the publication depends on with each feed's latest load state, share-class count, state, and the run reference once one exists.
- States: Planned → Dependencies met → Generating → Validated → Delivered, plus Held (egress validation blocked the send), At risk (a data precondition is observed unmet before the window), Nothing owed (the window discharged vacuously — empty scope or everything already delivered; never counted as a delivery) and Failed. A precondition scan that cannot observe coverage — the scope query failed, or the pipe maps no Openfunds fields — reports unknown: it never clears a standing data-not-ready warning and never paints At risk; the warning it raises names why coverage was unobservable. Colours follow the platform key: emerald delivered, blue in progress, amber attention, red failed.
- Cut-off: set an explicit cut-off time and timezone per subscription (Admin mode → Subscriptions); without one, the day's last scheduled fire time stands in, marked "(cron)". The countdown turns amber inside the final hour and red past cut-off.
- "On arrival" windows: subscriptions triggered by new data rather than a clock render as on arrival — their clock is the dependency, not a fabricated window time.
- Dependencies: each chip is an inbound pipe that has loaded data for this publication's entities, coloured by its latest run — emerald loaded today, amber stale or never loaded, red failed, blue running. The run a chip reads is the latest run of that pipe for this publication's own asset manager — on a shared template another asset manager's load never colours your chip, and never loaded means your own data has not arrived through it. If the pipeline service cannot be reached, the chip reads unknown: the board degrades honestly rather than guessing.
- Share classes: the count each window covers, from the same scope resolution a real delivery uses. Delta-scoped subscriptions read potential — eligible entities, never a precise send list, because a delta list is only fixed when the window fires. Rows for bundle pipes count bundles instead (marked "bundles"), and their dependency chip is the real one for that kind of publication: whether a released disclosure bundle exists in scope at the costs service — not inbound data feeds.
- New-launch instructions: when an instruction is in flight (queued, running, partial or failed) the board carries it in its own card above the rows — destination, asset manager, N/M file sets, share classes (and how many were already onboarded), state and the reason a set failed. An instruction has file sets, not windows, so it is never a subscription row and never counts in the day ledger's owed; manage it under Delivery → Pack (see 5.12).
- Delivery receipts: every export — bundle or classic — records a receipt per delivered item with the transport facts (SFTP remote path and byte count, email Message-ID and recipients, the receiver API's HTTP status), the content fingerprint, and one status ladder: stored → sent → delivered → acknowledged (with preview, partial, skipped, failed and blocked as the honest exceptions). "Sent" means handed to transport with every recipient accepted and no positive confirmation (email — a partially refused recipient list reads partial, never sent, and the refused addresses are recorded); "delivered" means placement was confirmed (SFTP upload completed, or the receiver's own API returned success); skipped means the window discharged with nothing owed — an empty scope or every bundle already delivered — and is never counted as a delivery. A receiver's confirmation can be captured against the receipt (acknowledge, with a reference), which is the terminal state. Receipts are scoped to your asset managers — you see and acknowledge only your own delivery evidence (each receipt carries the SFTP path and recipient addresses, so this scoping is a confidentiality boundary, not a convenience); platform-level evidence tied to no single asset manager is a platform administrator's to see and acknowledge. Bundle receipts carry the bundle and version, which is what makes delta windows precise: a bundle version already receipted to a destination is never re-sent by a delta subscription.
- Date: the board defaults to today; pick any date to see that day's plan.
- Close of day (the day ledger): the Close of day toggle turns any date's board into its ledger: what the day owed, what went out, what was held and why (with the actor), what carries to tomorrow. It reconciles — owed = sent + held + carried — against two independent records: the board's rows and the scheduler's own log of every window it decided was due that day. A window the scheduler fired must end the day with a run, a delivery receipt or a recorded skip behind it (an empty scope, a pipe not locked); one with none of those is unreconciled — the page names it in red with the fire time and reason, the close-of-day email carries DOES NOT RECONCILE in its subject, and the affected asset manager gets a critical alert on the bell — rather than the day rendering a plausible-looking total. A receipt stands as evidence even when its run record has since been deleted. A direct run from the pipe page (not started by a schedule) counts for a window only when exactly one of the asset manager's subscriptions covers that pipe and destination; when two do (a full feed and a delta feed, say) the run is counted for neither rather than for both. The same ledger is available as an emailable rendering, so the weekly operational meeting reads what the day wrote for itself instead of a pack someone assembled. One honesty rule tightened with receipts: a run whose file was generated and stored but never transmitted (the destination has no wired transport) no longer counts as sent — the ledger holds it with exactly that reason.
- Alerts & digest (your preferences): the Alerts & digest button opens your own alert shaping — a severity floor for the bell (criticals always reach you; the floor can never mute them), an opt-in for the close-of-day ledger by email, and which destinations your ledger covers. Preferences are per-user and self-service; each recipient's emailed ledger is scoped to their own asset managers, never the whole book. You may point the email at a different address from the one you sign in with — a colleague, or a shared operations mailbox — but it must be in your own domain. The ledger carries delivery data for every asset manager you can see, so the platform will not send it to an address outside your organisation; if you genuinely need an external recipient, that is an allow-list for a platform administrator to arrange, not a free-text box. Leaving the field empty always works and means "use my sign-in address".
- Pre-flight (validate before the window): run pre-flight on a row generates the publication against the current golden state and runs the destination's own egress rules — without persisting anything, without a run record, without delivering. A blocking value surfaces while there is still time to fix it, not when the window fires. The chip reads clean (emerald), N warnings (amber) or N blocking (red); results stay cached until new data lands for that asset manager (the moment its feeds arrive, the platform re-runs pre-flight in the background), so the board never regenerates output just to render.
Part VII — Screen reference: Admin mode
Admin-mode pages require the relevant role sections (Delivery pages: Outbound & Delivery; Configuration pages: Administration). Pages marked (super-admin) are restricted to platform super-administrators.
7.1 Destinations (Delivery)
Where data gets sent. See 6.16 for the console layout. Admin specifics:
Destinations are shared consumer infrastructure — one destination is fed by many managers — so every operator can see the list of destinations and their targets, but the connection details and the list of managers using a destination are shown to administrators only; a manager-scoped operator sees the name, adapter, channels and status, never the config. What a non-administrator can be shown of a config is an explicit allow-list of location-shaped fields (host, port, path, URL, recipients and the like): a key nobody has classified is withheld rather than disclosed, so a new setting is private until someone decides otherwise. Credentials for SFTP/FTPS destinations are withheld from everyone, administrators included — a stored secret reference reports only that one is set. Hermes always sees the non-administrator view, whoever is asking: an agent's context leaves the platform, so it carries the location fields it needs to answer questions and never a credential. Delivering a document through a target also checks that the target's destination is one your manager is subscribed to — an unrelated destination's target is refused.
- + New destination: name (required), adapter (Kurtosys API / SFTP / FTPS / HTTP POST / Email), adapter-specific connection fields, optional schedule (presets from every-15-minutes to weekly, or custom cron) with an Active toggle, and an environment classification — production (the default), sandbox, or mock. The classification is the destination's own, independent of the platform's: the final delivery adapter refuses released regulatory output toward a sandbox or mock destination, and DEMO output toward a production one — the last code that can stop a byte, not a UI rule.
- SFTP and FTPS connections hold credentials by reference, never by value. The structured fields are host, port, username, auth method, a secret reference (a Secrets Manager ARN or an
env:/local:reference — the platform stores the reference, resolves the material only at connect time, and never echoes either back), the pinned host key / certificate fingerprint, remote path, and an internal-network override (super-admin only). There is no password box and no raw-JSON escape hatch for these adapters, and a save that tries to smuggle a plaintext credential key is refused with the reason. A destination with no secret reference does not deliver: the run fails naming the destination and the cure (set the reference; a row still carrying an old plaintext credential is refused the same way and points at the migration script that moves it into the secret store). Credentials are never read from the destination record itself. - An email destination can send as the asset manager, not as the platform. Optional From address / From name put the tenant's own sender on the message (sent through the platform's mail account); a destination whose counterparty expects mail from the tenant's own domain can carry its own SMTP account — host, port, username and a secret reference for the password (a reference, never the password; a save that carries a plaintext SMTP password is refused with the reason, and the account is all-or-nothing: host, username, reference and a From address together). At send time the credential is resolved by reference; a reference that cannot be resolved fails the delivery naming the cause rather than quietly sending from the platform account under the tenant's name. The same save rules apply whether a destination is edited on this screen or through Hermes.
- Pinning a destination's host identity (first contact): save the connection details, then press Test connection. Against a host that has never been pinned, the probe stops before any credential is sent and returns the server's observed fingerprint — verify it with the counterparty out-of-band (they can read it off
ssh-keygen -lffor SFTP, or their certificate for FTPS), then press Confirm and save fingerprint. Until a fingerprint is pinned, the destination refuses to deliver — it reports the observed fingerprint instead of sending. If a pinned destination's server ever presents a different key, delivery stops with "HOST KEY CHANGED", nothing is sent, and the pin is not updated automatically: re-pin only after confirming the change with the counterparty, because a changed key is what an interception looks like. - Pack (the destination's new-launch pack): per-row Pack opens what a new share class needs to be onboarded at this vendor — the components (Insert form via a locked pipe, NAV history, latest documents, document list), the history depth, the file budget (the vendor's per-file limit, if known — never guessed; empty = the fallback of 50 share classes per file), the fallback count, the file suffix pattern, and whether an approved first production check instructs automatically. Editing the pack is super-admin (a destination is shared infrastructure); starting, retrying or cancelling an instruction is a write on the Asset Manager. Saving the pack creates the destination's pack pipe once and bumps the pack's version on every save; an instruction records the version it ran under. See 5.12.
- Guardrails: a schedule cannot be set Active without a cron (and clearing the cron deactivates it — no silent broken schedules); the scheduler only fires when the schedule is Active and the destination status is active; Delete only succeeds when nothing references the destination — if pipes, subscriptions, or delivery history point at it you'll be told to deactivate instead; changing the adapter does not migrate the connection config — re-enter it for the new adapter; an adapter name outside the wired set is refused at save, so a typo can no longer create a destination whose files silently stay in storage.
7.2 Subscriptions
The delivery matrix: which AM × which destination × which pipe, with what scope, on what trigger.
- Follows the AM picker. With an Asset Manager selected the page lists that Asset Manager's subscriptions only — narrowed by the server, never in the browser — and the subtitle names it; choose "All asset managers" for every subscription you can see. There is no separate AM dropdown on the page.
- Grouped by destination; each subscription shows scope mode (Full/Delta), trigger (cron / on-new-data / both), filter and ISIN counts, and its schedule (own cron, "inherits destination", or paused).
- Create: pick AM, destination, and a locked pipe (unlocked pipes are shown disabled with "lock in /outbound first"). The form's Asset Manager is pre-selected from the AM picker, so working "as" one Asset Manager never creates a subscription for another by accident; the pipe list offers that Asset Manager's pipes plus the shared generic templates.
- Per subscription: Execute now, Pause/Activate, edit scope (mode, trigger, and the "Which entities?" selector — everything in the AM's book / matching filters / an explicit ISIN list), a per-subscription schedule override, and Delete.
- How scope resolves (the two rules): entity filters AND each other — an entity must match every filter (the only any-of is the "in list" operator on a single field). An ISIN list is an override, not a filter: when both are stored, the filters are not applied — the editor shows them greyed with a note, the row chip reads "N filters (not applied)", and saving preserves both. The "Resolves to N share classes" readout under the selector calls the same resolver a real send uses (delta is applied at send time on top — it narrows, never widens), so a wrong scope is visible before it ships; if resolution fails it says so rather than showing a number. A delta window only advances when the push was confirmed delivered — a failed or blocked push holds the window open, so those rows are owed again on the next scheduled run instead of silently falling outside the delta.
- Scope Agent: describe the scope in plain English, Preview the interpreted filters, then Apply to the editor.
- A subscription with its own active schedule runs independently and is excluded from the destination's cron.
7.3 Email Channels
Maps inbound email addresses to Asset Managers so emailed files ingest under the right AM and can auto-execute a locked pipe. Create with the AM, the inbound address, an optional pipeline hint (matches the locked pipe's name; the email subject line can also route by pipe name) and the allowed senders — one per line, either a full address or @domain for everyone at that domain. Knowing the inbound address is not enough to file data into an Asset Manager. An emailed file is attributed to the channel's AM only when all three hold: the sender is on the channel's allowed list, the message passed SPF for the sender's domain, and it carries a passing DKIM signature from that domain (or a subdomain of it). Anything else — an unlisted sender, a channel with no allowed list, a failed or missing SPF or DKIM check — is stored unscoped (no Asset Manager, so no pipe can match it and nothing runs) and the channel's AM gets a warning on the bell naming the sender and the reason; an administrator can attribute the file by hand from Ingested Files. A channel showing none — files held in the Allowed senders column attributes nothing until the list is filled in. Pause/activate or delete per channel. The address must exist in the mail-inbound configuration (your platform team sets that up, and can restrict the inbound webhook to the mail provider's own network addresses).
7.4 Sources (inbound SFTP/FTPS)
One entry per counterparty connection — a source is a relationship (host, credential, directories, schedule), never a property of a pipe: every file a source fetches routes to whichever locked pipe for that Asset Manager matches its signature, exactly as emailed files do, and non-matching files quarantine with the reason and nearest-miss diff. Create a source with the AM, protocol (SFTP or FTPS — plain FTP is never offered), host, username and either a password (flagged as weak) or a Kairo-generated Ed25519 keypair (Generate keypair on the detail page shows the public half to send the counterparty; the private half is stored in the platform secret store and never displayed). Host-key pinning is mandatory: the first Test connection shows the server's SHA256 fingerprint — verify it with the counterparty out-of-band, pin it, and polling refuses to run until it is pinned; a changed host key fails every poll loudly rather than trusting the new key. Each watched directory carries its own filters (include/exclude globs, optional filename regex — a named feed group routes by pipe name like the email subject line — size and age bounds — with no per-directory maximum, the platform's standard 50 MB intake ceiling applies, the same limit as upload and email) and post-fetch rules (leave / move-to-archive with {yyyy}/{mm}/{dd} tokens / delete — delete applies only to successfully ingested files and requires the source's confirm-deletes flag; move on failure is recommended so a poison file leaves the listing). The intake ceiling is enforced during the download too, on bytes actually landed — a server that under-reports a file's size in its listing cannot stream a huge file past the limit, and a download whose landed size disagrees with the listed size is refused rather than ingested. A stability gate (unchanged mtime, or a completion-marker file like {name}.done) stops the poller reading files still being written; a server that reports no modification time is treated as not yet stable and held, so configure a completion marker for such a source. Polling runs on a per-source cron schedule (or manually — Poll now and Dry-run poll, which lists and reports without transferring anything); every file the poller touches leaves a row in the Fetch history tab with its outcome (ingested / duplicate / quarantined / failed / refused) and reason. A file that keeps failing is attempted a bounded number of times (default 3) then refused with an alert — press Retry on its ledger row to re-attempt after fixing the cause; a source whose connection keeps failing (dead credential, changed host key) pauses itself after 5 consecutive failures, alerts, and waits for Resume. Duplicate handling is content-based: the same bytes under a new name are not re-run, while a file overwritten in place is re-read. Fetched files appear in the Ingested Files inventory with the ⇄ sftp chip and the source's name as the sender. Visible with Files & Documents read; creating and editing sources needs Administration write (plus Files & Documents write — sources live in the files service, whose write gate covers every mutation). Deleting a source with fetch history is refused — deactivate it instead.
7.5 Policies
Conflict-resolution policies for the golden record, plus the Data Source registry.
- Policy cards: name, type (Last Wins = blue / Source Priority = violet), Primary badge, AM scope (a specific AM or "Default — all AMs").
- Create policy: name, description, type; for Source Priority, build the ranked source list (add, reorder, remove — highest priority first). Sources register themselves automatically the first time a pipeline executes, so a brand-new instance shows "Execute a pipeline first to register sources."
- Evaluate All: recomputes the golden record under a policy; reports how many entities changed.
- Data Sources table: every registered source with its type and priority, plus the Mark PPC toggle and destination picker (see 5.6).
You see policies (and sources) for the asset managers you can access, plus the platform defaults ("Default — all AMs"), which everyone can read. Another tenant's AM-specific policies are never listed. Creating and editing policies remains a platform-admin action.
7.6 Policy Stream
Read-only golden-record change history: enter an entity key (and optionally a field ID), and get the timeline of every change — old value → new value, the old and new source, the reason, and when it was evaluated.
7.7 Extraction Config
Controls which Openfunds fields the AI extracts from each document type (Prospectus, KID, Factsheet, Annual/Semi-Annual Report). Edit the field list inline (code, name, type, description, expected values), optionally override the prompt template, Save, or Reset to Defaults.
7.8 Doc Templates
Reusable AI extraction templates: name, description, AI model, classification keywords (how documents are matched to the template), the extraction prompt, and the target Openfunds fields. A template can be bound to a document type so registration triggers extraction automatically (see 5.7 — the platform default per type is set through the document-types API; each asset manager overrides it under Documents → Intake conventions).
Global templates (shared across all asset managers) can only be created, edited, or deleted by platform admins; you manage templates scoped to the asset managers you can access. Global templates stay readable by everyone.
7.9 Template Profiles (super-admin)
Cached outbound mapping profiles per vendor-template fingerprint. The first build of a Bloomberg/Morningstar/BDUP/Findatex template pays the AI mapping cost; locking registers a profile here so every future upload of the same template clones instantly. Deleting a profile just means the next build re-runs the AI; existing pipes are unaffected.
7.10 Openfunds Registry (super-admin)
Upload a new Openfunds release (the fields and changelog workbooks) to update the live field registry — active within a minute, with version history and rollback.
7.11 AI Usage
Read-only cost dashboard: total spend, tokens, calls, and errors, broken down per agent (Atlas, Hermes, Oracle, Argus, Chronos, Pulse…), with an expandable list of recent AI calls (cost, duration, who triggered them).
Monthly spend cap for the agent chat panels. An administrator can cap the agents service's monthly AI spend (AGENTS_AI_MONTHLY_COST_CAP_USD; 0 = no cap). The cap is enforced against the same ledger this page reads, at every agent query endpoint: once the month's spend reaches the cap, agent chats answer with the cap message (the spend, the cap, and what to do) instead of calling the model, until the new month or a raised cap. The Pulse dashboard briefing stays available — its spend still counts toward the cap.
7.12 Clients (super-admin)
Client organisations and their access. Create a client (Direct AM / Service Provider / Data Consumer), then on its detail page grant Asset Manager access (Full / Filtered / Subscription scope), toggle product subscriptions per AM, and see assigned users. The identifier code — the short code outlets and counterparties know the client by (ACME; letters, digits, _ . -) — is set when creating a client or edited inline on the Asset Manager's page; it fills {asset_manager_identifier} in delivered filenames and file headers.
7.13 Products (super-admin)
The catalogue of subscribable products/services, grouped by category (Delivery, Document Production, Data Management, Compliance).
7.14 Users (super-admin)
Create and manage user accounts: email, display name, password (minimum 8 characters), access role (the Part III roles), client assignment, and the super-admin toggle. Password reset lives here: edit a user and enter a new password (blank keeps the current one). This is the only reset path — there is no self-service "forgot password".
7.15 Roles (super-admin)
The permission-matrix editor: per role, tick Read/Write per section. Write auto-includes Read; removing Read removes Write. System roles (Administrator, Operator, Analyst, Viewer) can be edited but not deleted; a custom role can only be deleted after its users are reassigned.
7.16 AI Models (super-admin)
Which AI model each platform function uses (Atlas builds, Hermes chat, document extraction, briefings…), with per-function Change/Reset against a live model catalogue, and a read-only view of each AI provider's transport configuration (relevant to data-residency requirements).
7.17 Custom Fields (super-admin for platform-wide; admin for AM-scoped)
Define fields outside the Openfunds registry: scope (an AM or Platform), field ID (AM_42:product_code pattern), display name, data type, the entity level it belongs to, and accepted values for enums. A custom field cannot be deleted while any active pipe mapping still targets it.
7.18 TC Parameters (admin)
Transaction-cost configuration for the selected Asset Manager — the same tca.tc_parameters store a parameters pipe loads (see section 5), edited by hand. Parameters change calculation behaviour: MocFund lists Market-on-Close funds (arrival price from the first fill; trades without an arrival timestamp raise a readiness warning), AdlExpected overrides the anti-dilution expectation (without a row it derives from the fund's own OFST454300 declaration; value true or blank = figures required — a missing figure blocks EU PRIIPS readiness; value false = no levy operated), AdlScopeExclusion lists funds whose ADL figures are kept but marked excluded, TcNegativeCostPolicy and TcLookthroughMissingPolicy pin the per-regime calculation policies, and the methodology-routing set — TcFundInceptionDate (the fund's authoritative operating age), TcMethodologyForce, TcMinTradesForActual, plus the estimate's rule pack TcSpreadCostTable / TcSpreadTableVersion / TcTurnoverEstimate — drives which cost methodology each run gets (see the Regulatory Calculations section). The validation library's switches — TcItcThreshold, TcMinorUnitCheck, TcAumConflictThreshold, TcFairValueTolerance (the pre-agreed trade-price vs fair-value exception tolerance) — configure the warn-first data checks that run with every calculation. SriObligorCqs pins a fund's obligor credit quality step (0–6, per your ECAI mapping, any maturity adjustment baked into the step) for the risk regime's credit-risk measure — an unpinned fund keeps the disclosed CR1 default. SriScenarioBenchmark selects which linked benchmark entity supplements a short share-class history for the performance scenarios when the fund's canonical model links more than one (with exactly one, no selector is needed; with several and none pinned, the engine refuses to guess and says so). The UK CCI risk score's levers: CciScoreFloor (the categorical floors the platform cannot see for itself; loss-beyond-investment needs no pin — the engine assigns the terminal 10 from the classification), CciLowLiquidity (the low-liquidity +1, with the engine applying the skip-at-9-or-above carve-out), CciScoreAdjustment (the documented judgement adjustment; the pinned value plus the run's evidence form the required record), and CciWeeklyNotObtainable (the recorded weekly-not-obtainable evidence that justifies a monthly-cadence score — without it a monthly-based run carries a warning asking for it). MdProviderConfig configures the market-data fetch seam per Asset Manager (key1 provider / credential_mode) — see the TC Readiness section; it is deliberately non-secret. TcRegulatoryCycle puts a regime on a calendar cycle (key1 = regime, value = monthly / quarterly / annual / off) — the daily runner fires the sweep once per completed period boundary. The scenario-bearing regimes (EU PRIIPS, PRIIPs SRI) must run at least monthly — the regulation's own minimum for performance scenarios; a slower setting still runs but is flagged NON-COMPLIANT on the cycle plan and as a red chip on the operations cockpit, with the fix named (set the cycle to monthly). See Regulatory cycles in the Regulatory Calculations section.
The screen explains itself — and checks the configuration (D-13/D-14). Every group the platform consumes carries a registry entry: a plain-language label (the technical name — the client's own vocabulary, what an operator will search — sits beside it), a category (instrument masters · fund-level configuration · methodology & regime · vocabularies · thresholds · platform) the screen groups by, and a declared key shape. A registered group with no rows says not configured (the platform supports it and nothing is set) — different from not in the platform registry (rows exist but the platform holds nothing to describe them with; flagged, and worth documenting). A Configuration health panel at the top validates the parameters like the client feed they are: key shape (an ISIN-keyed master with non-ISIN keys has silently dead rows), key resolution by declared type (a fund list resolves against the book's funds — suffixed codes like WHSEAP_302 resolve on their base; an instrument-type list resolves against the type master, never misread as fund codes), vocabulary (mapped values against their declared value set), range (price factors positive, tolerances inside 0–1, thresholds ordered) and coverage (traded instruments missing from the classification master — the usual UNCLASSIFIED cause). A configured control whose keys resolve to nothing is flagged blocking — "this control cannot fire" — with the unmatched key space named, because "26 unmatched, all SE*/SH* prefixed" diagnoses a code-space mismatch where a bare count cannot.
The page lists one card per group, collapsed by default with the group's plain-language description and active-row count as the headline — expand a card for its rows. Groups not in the platform registry are flagged (allowed, but nothing consumes them yet).
- Add or update: pick a group (the known groups are described inline; "Other group…" accepts any name — unknown groups are allowed but flagged as not yet consumed by the platform), enter the key (usually a fund code), an optional value and validity dates, and save. Saving uses the same last-write-wins key as a re-ingested parameter sheet (asset manager + group + keys), so a hand edit and a file load converge identically — there is never a duplicate row for the same parameter.
- Deactivate keeps the row (status inactive) rather than deleting it; every reader only consumes active rows, and the row stays visible to audit. A parameter's identity (group and keys) is immutable — to "rename" one, add the new row and deactivate the old.
- Every write lands in the Audit Trail attributed to you.
7.19 Quarantine (super-admin)
The holding area for unmatched automatic arrivals — see 5.3 for the full resolution guide (Retry match / Rebuild for drift / Build new pipe / Reject), including the advisory Identify client action (Nexus suggests which configured client a held file belongs to; you always confirm by building and locking the pipe yourself).
7.20 Danger Zone (super-admin)
Irreversible bulk deletion of data — platform-wide or for one Asset Manager. Requires typing an exact confirmation phrase before the button enables, and reports exactly what was deleted. This cannot be undone. It exists for test/demo resets; treat it accordingly. The two API-only reset routes behind it (/admin/cleanup-pipeline-data, /admin/cleanup-all-test-data) are held to the same standard: each requires its own typed phrase in the request body, and on a deployed environment they are refused outright unless the platform operator has set FDAP_ALLOW_DESTRUCTIVE_ADMIN=1 for a demo estate — a script, an agent action or a mistyped call can never wipe a live platform.
7.21 Asset Managers
Reached from the sidebar footer (visible to all; create/scrape/delete actions are super-admin). The list shows every AM with search; Add Asset Manager includes a Discover button that auto-detects how an AM's website can be scraped. Each AM's detail page is its console: document counts and extraction progress (with Extract Pending / Re-Extract / Retry Failed actions), discrepancy stats (Detect / Analyze — platform-wide maintenance, super-admin only), the scraper configuration card (platform-specific settings, auto-detect), and Run Scrape with live job status. Document extraction is guarded: a PDF whose text parse exceeds the worker's memory or time budget is marked failed with the reason (it no longer takes the extraction worker down with it), and a document whose earlier runs repeatedly died mid-extraction is refused automatic retry — investigate the document, then use Retry Failed to re-queue it deliberately.
Part VIII — Appendices
A. Quick answers (FAQ / troubleshooting)
I can't see a page or button I expect. Your role doesn't include that section, or the AM picker is filtering it out. Check the picker first; then ask your administrator to review your role.
"Session expired." Normal after a period of inactivity — sign in again; nothing is lost.
I'm locked out. Ten failed logins locks the account for ~15 minutes. Wait, or ask an administrator to reset your password (Users page). There is no email-based reset.
A file I emailed never showed up. Check Ingested first (search the filename), then the bell / Quarantine — if the file's shape didn't match a locked pipe, it's held there with the reason. See 5.3.
A run failed. The bell alert deep-links to the run. Expand the error in Run History — errors are grouped and many have inline Fix actions. You can also ask the page's agent "why did run INB-0000042 fail?" and get the actual error text.
Wrong data reached the golden record. Revert the offending run (pipe → Run History → Revert), fix the mapping, re-execute. See 5.8.
A value looks wrong in the Explorer. Check its source badge and confidence; use the edit pencil for a manual override, or trace its history in Policy Stream. If it's an unknown-value translation problem, it will be waiting in Lookup Review.
The destination says our file is malformed. Check the outbound run's validation findings first — the egress validator usually catches format violations pre-send; if findings were warnings, review them with Hermes.
Who changed X? Audit Trail — filter by actor, type, or date; every human and AI action is recorded.
B. Status colour key (everywhere)
| Colour | General | Pipes | Runs | Quality |
|---|---|---|---|---|
| Emerald | good / active / valid | locked | completed | resolved / approved |
| Amber | attention / pending | draft | completed with errors | pending / warning |
| Red | error | failed | failed | material / rejected / expired |
| Blue | info / running (pulses) | building | running | expected variance |
| Violet | AI / review | review | — | in review |
C. Notification types
| Alert | Severity | Meaning |
|---|---|---|
| Run failed | Critical (also emailed) | An inbound or outbound run failed outright |
| Completed with errors | Warning | The run finished but some rows failed |
| Schema drift | Warning | A feed's shape changed vs its locked pipe — the alert names the publications it threatens, the widest share-class exposure, and today's first affected cut-off (live countdown on the Publication Board) |
| Quarantine | Warning | An automatic arrival matched no locked pipe |
| Missing NAV / completeness | Warning | Expected data is overdue for an entity |
| Missing required document | Warning | One per asset manager per document type — N share classes without a current PRIIPS KID; clears itself on the first daily scan that finds the book complete |
| Document validity | Warning | A document is expiring or expired |
| Delivery failed | Warning | An outbound push to a destination failed |
| Precondition unmet | Info → Warning → Critical | A publication window's required data isn't held at the required effective date. Severity follows the headroom: info while slack remains, warning inside the final two hours, critical past cut-off. Auto-resolves the moment the data lands |
| Inbound file refused | Error | A file on a transfer source failed its bounded retries and is refused for automatic retry — open the source's Fetch history, fix the cause, press Retry |
| Inbound source paused | Error | A transfer source hit its consecutive-poll-failure ceiling (dead credential, changed host key, missing directory) and paused itself — fix the connection and press Resume |
| Channel silent | Warning | An unattended inbound channel (email/SFTP/API) has received nothing for 48 hours — check the channel (credential, drop path), not the individual feeds |
Identical alerts collapse with a ×N counter. Muting an AM silences its warnings/info only — criticals always raise.
D. The agents at a glance
Pulse (platform briefing) · Atlas (inbound mapping) · Nexus (identify AM) · Oracle (fund queries) · Chronos (golden record & time travel) · Sentry (quality) · Argus (files, documents, pipelines) · Hermes (delivery — can act) · Scope Agent (plain-English delivery filters) · Sentinel (audit) · Steward (the Regulatory Operations work queue) · Doc Review Agent (extraction review) · Lookup Proposer (background — feeds Lookup Review) · Mentor (this guide, on the Help page).
End of manual. Feedback and corrections: contact the Kairo team.