Plugins, roles and grants¶
Everything a firm builds on Open Meridian is a plugin: a connector to a broker, an analytics page, a bot, a tool a trader had an AI agent write. This page explains what a plugin is, what it may do, how that is decided, and how people reach it.
To write one, start with Your first plugin. To choose its roles, see Roles.
A plugin is a small program beside its sidecar¶
A plugin runs in a pod of its own, beside a sidecar that Open Meridian provides. The sidecar listens on the pod's loopback address, and the plugin talks to it and to nothing else.
- It never learns the bus's address, and never holds a broker credential. The credential is mounted into the sidecar's container, not the plugin's.
- It never discovers another plugin. Plugins that work together do it over the bus, each through its own sidecar.
- It cannot forge who it is. The sidecar stamps the plugin's identity, the time and each message's identifier onto everything the plugin sends.
This is what makes a plugin small. Access control, transport, correlation and health are the sidecar's, implemented once, so a plugin author writes against one local surface and gets all of them. That surface is the interface in meridian-schema; the operations a plugin's roles may take are listed in Typed operations, and in Python the SDK wraps them.
Admission fails closed: nothing a plugin sends is admitted before the sidecar has loaded what it is allowed to do. The plugin is told its grants when it registers, so a plugin missing a grant fails at start, where somebody is looking, rather than at its first refused message.
Plugins hold no state¶
A plugin is ephemeral. It may be stopped, restarted or moved to another node at any moment, and it keeps nothing that has to survive that: no local database, no file it expects to find again. What it needs, it reads from the deployment when it starts. What must last — holdings, statements, instruments, configuration — lives in the deployment's own stores.
A plugin at the edge, one holding connectivity, custody, servicing, confirmation,
settlement, data or reporting, may also own storage the deployment
grants its instance alone, for the raw records its vendor sent it, and
rebuild from it; no other plugin reads it. Its older records may move to an archive the deployment provides:
see The archive.
Roles: what a plugin is for¶
A role says what a plugin is for, and it is the only thing that decides
what the plugin may publish and subscribe to on the bus. Roles come from a
fixed list of fifteen, and a plugin declares a set of them. An order and
execution management system is oms and ems; a fully automated trading
system might be signal, portfolio, oms and ems. A plugin holding
several roles still has one sidecar and one credential, and holds the union of
their grants. A person is granted a level on each of its roles apart; see Access per role.
| Role | What a plugin with it is for |
|---|---|
compliance |
Pre- and post-trade compliance rules |
confirmation |
Post-trade confirmation and affirmation with external matching services |
connectivity |
Broker and venue connectivity |
custody |
Custodian, prime broker and broker statements, positions and cash, into the street store; reconciling them with the book is operations' |
data |
External data ingress |
ems |
Execution management |
oms |
Order management, including tax-lot selection |
operations |
The post-trade pipeline: matching, servicing, reconciliation, settlement |
portfolio |
Portfolio construction |
pricing |
Pricing models: answers the analytics component's batches for the models its version declares, from exactly the market objects each model declares |
reference |
Reference and terms data: a security's terms gathered from sources, reconciled, and offered to the instrument store with their provenance |
reporting |
Reporting, analytics and valuation |
servicing |
Non-trading transactions: corporate actions, lifecycle events, coupons |
settlement |
External settlement rails |
signal |
Signal generation |
In words, a plugin holding a role is a data plugin, a custody
plugin, an operations plugin, a reporting plugin and so on, the
code name beside it where a person configures access. Order matching, at an
ems or a connectivity plugin's crossing venue, has no role of its name.
Every plugin of a role has the same first-use path and the same pages in the same order, so someone who knows the role knows the plugin: see Using a data plugin, a custody plugin, an operations plugin and a reporting plugin.
portfolio, oms, ems, connectivity and compliance have the order
path's operations, each
role's duties kept apart in the records whichever plugins hold them; see
The order lifecycle.
Each role's entry, with its persona, its duties, what it leaves to which other role and what it holds at the current contract, is on Roles, with the principles a plugin must fit and the method that maps an idea for a plugin to the least roles it needs. Because nobody can change a role's grants, an idea that needs a grant no role has is reshaped to fit, never given one.
Nothing else is a role. The deployment's own components — the conductor, the
dashboard, the street and instrument stores, the book, first-run and the launcher — are
not roles, and no plugin may declare one. In particular there is no admin
role: administering a deployment is the dashboard's, and no plugin can
administer the deployment it was installed into. The admin level below is a
person's, on a plugin, and has nothing to do with roles.
What each role holds
A role holds exactly the topics a workflow in Open Meridian's contract
names it for, listed on Roles, generated from the contract.
servicing and settlement hold only the reads every role has until
their workflows are built. A plugin holding only such roles, or naming
no role at all, is admitted rather than refused. It can still serve a
page.
Grants are generated, never written¶
Nobody writes a plugin's grants. Open Meridian's contract lists, for every topic on the bus, which roles and components publish it and which subscribe. A role's grants are derived from that list, and so are the broker's permissions. There is no grant file for a deployment to edit or forget to mount, and no second policy that could disagree with the first.
This is also why a role is never a code path. The sidecar enforces a role's grants and nothing in the runtime behaves differently because of it.
Who may use a plugin: admin, read or write¶
Roles decide what a plugin may do on the bus. Who may use it is decided separately, by the deployment, and it is the same three levels for every plugin:
admin: the person configures the plugin, its settings and its own pages atadmin, and sees no account's data.read: the plugin may show the person what it reads, cut to the accounts they may read.write, which includesread: the plugin may also act for the person, sending commands for them, on the accounts they may write.
A person may hold admin and, beside it, read or write. A deployment
admin grants these in the deployment's access groups: an entry names a plugin
and a level, and one of the plugin's roles, so on a plugin holding custody and
operations a person may write in one and only read the other. How that is
granted is described in Access.
A plugin names no parts of itself for people, so that every plugin is administered the same way and nobody meets a new vocabulary with each one.
Declaring them¶
A plugin declares its roles in its own pyproject.toml, where
meridian plugin new puts them:
meridian plugin upload refuses a role outside the list, or a component's
name, before anything is sent. It also refuses a [tool.meridian] that
declares tags, even an empty list. The full format is in
the plugin manifest reference.
The catalogue: each deployment's own registry¶
Every deployment has its own registry, inside it, holding the plugins uploaded to that deployment and nothing that reaches any other.
A deployment admin uploads a plugin with meridian plugin upload, signed in
from a terminal. The CLI builds the image on the developer's machine and sends
it through the dashboard, layer by layer; layers the registry already has are
skipped, and the shared base image for each SDK version is held once for every
plugin built on it. The metadata — roles, whether it serves a page — is
recorded with the version, and so is its declaration: its settings, its pages and the tools
they make, whether it reads external accounts, and its documentation, the vendor's sections
from the plugin's docs/ folder, each named by its digest. A plugin registers exactly the
declaration its version was uploaded with, or its sidecar refuses it, so what a deployment admin
read before launching is what runs. See
Write a plugin's documentation.
A recorded version is never replaced. Uploading a name and version that is already recorded is refused. To change a plugin, raise its version. A deployment admin may retire a version nothing runs or names: it is no longer listed or launchable, its image is released from the deployment's registry, and its record is kept.
The deployment's own registry is the Local one. A plugin may also come from the
platform's Community registry, listed there as
namespace/plugin, and is then pulled into the Local registry by digest and
verified before it is recorded. See Registries.
Launching, and approval¶
Uploading a plugin runs nothing. Running it is a separate act, launching, and launching is where its roles are approved.
meridian plugin launch <name> <version> --instance <id> shows the roles the
version asks for, and runs it only once the deployment admin approves them. The conductor checks the launch against its catalogue, records it with
who approved it, and only then asks the launcher to start it. The launcher
creates one Deployment in the chart's plugin shape, from an image in the
deployment's own registry, and nothing else.
Each launch is an instance, with an identifier chosen at launch. One
plugin may be launched more than once — snaptrade-1 and snaptrade-2 from
one image — and each instance has its own pod, its own broker credential and
its own page. meridian plugin stop <instance> is launching's inverse.
Launching
A plugin is uploaded from the CLI, and launched and stopped from the dashboard's Plugins page or the CLI; see the CLI reference.
Its limits, shards and standby pod¶
A plugin reaches core only through its sidecar, which holds the plugin to
its share: at most so many requests in flight at once, so many a second
with a second's burst, and so many MiB of answers held or paged in flight
for it. The deployment's chart sets them for every plugin
(sidecar.limits.inFlight, requestsPerSecond and answerMiB; 16, 400
and 32 by default). Past one, only that plugin is refused, with a
retry-after, and the SDK (0.36.0) waits it out. A launch may give an instance its
own:
- The Launch sheet, on the dashboard's Plugins page, has Limits at
its sidecar: Requests at once, Requests a second and Answers
in flight, MiB, each left empty for the deployment's. Under them,
Standby pod, and Shards where the version declares more than one
(
most_shards), at most that many. - The CLI:
meridian plugin launch <name> <version> --instance <id>with--in-flight <n>,--requests-per-second <n>,--answer-mib <n>,--shards <n>and--standby. It shows each beside the roles before it asks, and after the launch reads back what the deployment kept.meridian plugin listshows each launch's limits, shards and standby. - An agent:
dashboard__launch_plugin'slimits,shardsandstandby(see Core's tools).
A value that is not a whole number above 0 is refused naming its field
(limits.answer_mib), and shards past the version's most_shards are
refused naming shards. Each launch is its own record, with what it asked.
The deployment keeps the shards and the standby pod on the launch, and the plugin runs one pod today.
A plugin's page¶
A plugin that declares interface = true serves a page. Each instance's page
has an origin of its own:
A separate origin per instance is what stops a plugin's script from acting as
the person on the dashboard, or on another plugin's page. It is why a
deployment needs a wildcard name, *.plugins.<host>, and why its address must
be a name rather than an IP address. On a laptop, any name under .localhost
works with nothing set up.
A person opens a plugin from the dashboard's home, at one of the levels they hold (see Manage, Open and View). The dashboard hands the browser a one-time code, which the plugin's host exchanges for a session of its own, carrying that level and ending when the dashboard session it came from ends. On every request after that:
- The dashboard checks the person's access against the records as they are now, and that they still hold the session's level.
- It signs an assertion: who the person is, the session's level, and what
that level reaches on this plugin — none of their accounts under
admin, the accounts they may read and the accounts they may write underwrite, the accounts they may read underread— and their level and accounts on each of the plugin's roles within it, for this instance only, valid for 60 seconds. - The plugin's sidecar verifies the assertion and hands the plugin the verified claims. A request that fails verification never reaches the plugin, and anything the request carried claiming to be the caller is removed.
The plugin can refuse more than it was handed; it has no way to permit more. Being a deployment admin opens no plugin: the home offers a plugin only at a level the person holds on it.
Manage, Open and View¶
The dashboard's home, Your plugins, lists every plugin the person holds a
level on, admin included, with a button for each level held:
| Button | Level | What it opens |
|---|---|---|
| Manage | admin |
Its Summary, Settings and a tab for each table setting, and the plugin's pages at admin: its configuration, such as connections and account links. No account's data |
| Open | write |
Its pages at write, acting on the accounts the person may write |
| View | read |
Its pages at read, read-only. A writer is offered View too |
A button opens the plugin's area, at /plugins/<instance>?level=admin,
write or read: one head, and one tab row holding the pages the plugin
declared at that level, in the order declared, with the page asked for in a
seamless frame below. On a plugin holding several roles, a page is in the
tab row when the person's level on one of the roles it
serves is one of its levels, and each button's title on the home names the
roles it reaches. A plugin that declares no page at write or read has
one there, its /.
The head is the same on every tab and at every level. On the left are the house, back to the home, the plugin's name and its status dot. On the right are the page's actions, such as a Refresh drawn as a circular arrow, then the Manage | Open | View switch of the levels the person holds, by which they move between them, as on the home; the switch is always the rightmost, so an action never moves it. Under Manage the dot is the plugin's health on the dashboard's own tabs and on any page that tells none; at Open and View it is the page's. On a phone the head stays one row: the switch is a menu naming the session's level and listing the levels held, and a long name is cut with an ellipsis. See Inside the dashboard's frame.
Under Manage the dashboard draws tabs of its own before the plugin's, and Manage opens on the first. At every level the dashboard also draws the Documentation tab, last: the vendor's sections and the Reference drawn from the version's declaration (see Where people and agents read it). A stopped instance keeps Summary, Settings and Documentation.
- Summary: the plugin's status, which is core's to say (its health and why, the version running and the interface version it registered with, any warnings, and what it received and did not carry), then the figures the plugin reports about its own work, each a tile, such as SnapTrade's Connections, Accounts reached and Last read. A figure is a count, a decimal, a text or a time, with an optional as-of, a state (ok, warn or error) as the tile's mark and a why as its note. A plugin reports at most 8, on its heartbeat; a list past any bound is refused, never cut. A figure names no account and carries none of an account's data. See Figures.
- Settings: the plugin's settings form, the same as in the dashboard's admin view of it. A plugin declares its settings in its code and an admin of it sets them here, the one place a setting is set: a plugin reads its settings and never sets one. Its settings are grouped in tabs (Required, Optional, and Developer on a development deployment), and its head says on one line who last changed them. Each change is its own record naming who made it. A setting serving several of the plugin's roles is set only by an admin of every one, and shown read-only to an admin of some of them.
- A tab for each table setting, after Settings and titled with the setting's label, such as SnapTrade's Plan-code links and Cash links: the table's entry grid alone, typed columns checked cell by cell, saved through the same checks as the form. See Set a plugin's settings.
So a plugin builds no summary page of its own. The health a plugin reports stands, with its why, until it reports again: one that says it is not healthy stays so on Summary until it says it is.
From a terminal, meridian plugin open --level manage, open or view does
the same; without --level it opens at the first level held, Manage before
Open before View. See meridian plugin open.
Each session carries one level, so Manage, Open and View are separate
sessions, and a person moves between them by the buttons. A page serves only
the levels it is declared with: a page at write and read asked for in a
Manage session is refused.
A page looks like the rest of Open Meridian because it is built on the
plugin UI kit, which the dashboard serves on the plugin's own host at
/.meridian/ui/<version>/, answering any 0.x version with the newest 0.x
kit it carries. The kit brings the brand's type and spacing, each person's
colour scheme, and components such as a data grid and the map a plugin links
its external accounts with. Every page sits in the area in a seamless frame:
the page keeps its own origin, and the frame has no border or scrollbar of
its own and is as tall as the page. See
Build a plugin's page.
What every plugin has, whatever its pages, is in the dashboard's admin view of
it, at /admin/plugins/<instance>: Overview (its health, what its
settings still need and its external accounts' sync state), Settings (its
form, for its admins) and Access (who holds which level on which of its
roles). Its own
pages are not there: they are in its area, reached from the home, its
configuration pages under Manage.
Reads and writes on behalf of people¶
A plugin reads as itself, for every account anyone may read through it — its read scope — and shows each person only the accounts their assertion allows. That per-person cut is the plugin's to get right; core bounds it to the plugin's scope, so a mistake can show a person another user's accounts on the same plugin, never an account outside it.
Writes do not rest on the plugin. A command a plugin sends for a person
carries that person's assertion, and the sidecar admits it only when the
person may write the account it names and the account is in the plugin's
write scope, and they hold write on a role of the plugin whose grants
include the command, and otherwise it refuses,
naming the role. The person is stamped on the change, so it is recorded as
theirs. The plugin's own grants remain the ceiling throughout. Scopes are
explained in Accounts.