Command line¶
meridian is Open Meridian's command line. It brings a deployment up where you have a terminal, shows how it is running, rolls it back, signs you in to one that is already running, and brings plugins into it: uploading, launching, stopping, and developing them live, and releasing and publishing them to the platform's registries.
Nothing in a cloud install depends on it. A marketplace listing's form and the deployment's own wizard are the whole path there; meridian makes the same things convenient from a terminal. Where it drives helm or kubectl, it drives your own, and what it tells you to run next is a meridian command, never helm or kubectl: you never run them yourself.
This page describes release 0.1.60 of the command line. meridian --version says which one you have. For installing it, see Install a deployment.
Synopsis¶
meridian doctor
meridian up --id <id> [options]
meridian down [--release <name>] [--delete-namespace]
meridian upgrade-deployment [--release <name>] [--chart-version <v>] [-f <file>]... [--set <key>=<value>]... [options]
meridian upgrade-deployment --enable-checksums [--release <name>] [--yes]
meridian rollback-deployment [--release <name>] [--to-revision <n>] [--timeout <d>] [--yes]
meridian backup [--release <name>] [--to <dir>] [--timeout <d>]
meridian backup key [--to <file>]
meridian restore <file> --identity <file> [options]
meridian restore --to-time <time> [--release <name>] [--timeout <d>] [--yes]
meridian restore --to-archive-end [--release <name>] [--timeout <d>] [--yes]
meridian status [--release <name>] [--json]
meridian status --profile [--json]
meridian logs <component> [--release <name>] [--since <span>] [--previous]
meridian connect [<address>]
meridian sign-out [<address>]
meridian plugin new <name> [--into <dir>] [--role connectivity|custody|data|operations|pricing|reporting]
meridian plugin check [--dir <dir>] [--run-tests] [--json]
meridian plugin migrate [--to <version>] [--dir <dir>] [--image <ref>] [--force] [--run-tests] [--json]
meridian plugin upload [--dir <dir>]
meridian plugin list
meridian plugin launch <name> <version> --instance <id> [--in-flight <n>] [--requests-per-second <n>] [--answer-mib <n>] [--shards <n>] [--standby] [--yes]
meridian plugin stop <id>
meridian plugin dev --instance <id> [--dir <dir>] [--yes] [--json] [--release]
meridian plugin logs --instance <id> [--since <revision>] [--json]
meridian plugin events --instance <id> [--since <revision>] [--follow] [--json]
meridian plugin open --instance <id> [--level manage|open|view] [--print <path>] [--json]
meridian plugin release <version> [--registry community|verified] [--organisation <namespace>] [--dir <dir>] [--timeout <d>] [--details]
meridian plugin publish --registry community|verified --tag <vX.Y.Z> [--organisation <namespace>] [--dir <dir>] [--timeout <d>] [--details]
meridian plugin key setup [--hardware] [--organisation <namespace>] [--dir <dir>]
meridian plugin key register <file.pub> | revoke <key-id> --reason <text> [--not-trusted-after <time>] | list [--organisation <namespace>]
meridian plugin download <namespace>/<plugin>@<version> [--registry community|verified] [--to <file>]
meridian plugin upload --bundle <file> [--deployment <addr>]
meridian plugin install <organisation>/<plugin>[@<version>] --note <why> [--registry community|verified]
meridian plugin install --pack <pack> --note <why> [--with <plugin>]... [--without <plugin>]... [--yes]
meridian plugin pack list [--json]
meridian plugin pack show <pack> [--json]
meridian plugin pack update <pack> --note <why> [--yes] [--json]
meridian plugin pack remove <pack> --note <why> [--yes] [--json]
meridian plugin gc [--release <name>] [--timeout <d>] [--yes]
meridian upgrade [--to <version>]
meridian uninstall [--yes]
meridian --version
meridian --help
meridian <command> --help
meridian <command> --help prints that command's own lines and flags, and the global options; meridian plugin <command> --help does the same for a plugin command. A command it does not know prints the whole usage.
How arguments are read¶
- A flag that takes a value may be written either way:
--params first-run.yamlor--params=first-run.yaml. - An unknown flag is refused, not ignored, and so is a stray word after a command that takes none.
meridian up --no-docterexits 2 rather than installing without the checks. - A flag given with nothing after it is refused rather than defaulted.
- When a flag is given twice, the last one counts. The exception is
-f/--values: every one is kept, in the order given. --releasemeans two different things. Afterup,down,upgrade-deployment,rollback-deployment,backup,restore,statusorlogsit takes a value, the Helm release's name. Afterpluginit takes none, andplugin dev --releasereleases the code as a version, with one exception:plugin gc, the one plugin command that names a Helm release, takes its name.
Global options¶
These are read before any command runs.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
-n, --namespace |
<name> |
meridian |
The Kubernetes namespace the deployment goes in. Used by doctor, up, down, upgrade-deployment, rollback-deployment, backup, status, logs and plugin gc. restore reads it as where to restore, and defaults to the namespace the backup was taken in; restore --to-time as the running deployment's, meridian by default. |
--platform |
<url> |
https://open-meridian.com |
The platform the deployment reports to. Used by doctor and up. up passes it to the chart only when you give it. plugin release, plugin publish, plugin key and plugin download sign in to it. |
--image |
<ref> |
ghcr.io/open-meridian/meridian-runtime:latest |
The runtime image. Used by doctor and up. up passes it to the chart only when you give it. plugin migrate reads it as something else: the SDK image its steps run in (see meridian plugin migrate). |
-h, --help |
Print the usage text and exit 0. After a command, that command's own lines and flags. | ||
-v |
Reserved: it prints a note and is otherwise ignored. |
Exit codes¶
Every command exits 0 when it succeeds. What a non-zero code means depends on the command:
| Code | Meaning |
|---|---|
0 |
Done. |
1 |
Refused or failed. The reason is on stderr. |
2 |
Asked wrongly: an unknown command or flag, a missing argument, or an argument of the wrong form. Nothing was done. |
3 |
plugin commands that act on a deployment (upload, list, launch, stop, dev, logs, events, open): this computer is not connected to the deployment, or its delegation was revoked or has lapsed. The message names the meridian connect to run. |
A plugin command exits 3 in three cases:
- when this computer is not connected to the deployment, or is connected to several and
--deploymentdoes not pick one; - when the deployment answers
401 Unauthorized: the delegation was revoked or has lapsed, its directory groups are older than the deployment allows, or it is one the deployment does not know. That includes the deployment's registry, whichplugin uploadpushes through. The message says which, and ends with themeridian connectto run, for exampleyour delegation to this computer at https://meridian.localhost was revoked: `meridian connect` to sign in again; - when refreshing the access token is refused: the delegation was revoked or has lapsed, its directory groups are too old, or a refresh token was presented twice, which revokes it (see The delegation).
An access token that has only expired never exits 3: the command refreshes it and asks again.
A script or an AI agent should treat 3 as "ask the person to connect again". Retrying won't help.
--json
The CLI README says every command takes --json. The parser does accept --json on every command, but only status, plugin check, plugin migrate, plugin dev, plugin logs, plugin events and plugin open change their output for it. Every other command prints text whether or not you pass it.
meridian doctor¶
Checks whether this machine and this cluster can run a deployment. It changes nothing.
The checks run in this order:
- Helm is present, 3.14 or newer:
upgrade-deploymentneeds its--reset-then-reuse-values, so a machinedoctorpasses can upgrade what it installs. - The cluster is reachable, and you have the rights to install into the namespace.
- There is a storage class for the deployment's key.
- No node is short of disk.
- The runtime image can be pulled from here.
- Each plugin launched in the namespace runs the sidecar its deployment's components run. One that does not is
worth, with the commands that relaunch it; it never stops an install. - Where a deployment runs in the namespace, the database it reaches has data checksums and
full_page_writeson, as its conductor reads them. Either off isworth: for a database the deployment brought without checksums, withmeridian upgrade-deployment --enable-checksums, which turns them on; for one somebody else runs, with where they are set. Both on isok. Nothing is said where no deployment runs there. Added in CLI 0.1.59. - The platform is reachable, and this machine's clock is within the tolerance a signed assertion allows.
Each result is one of:
| Result | Meaning |
|---|---|
ok |
Nothing to do. |
stops |
Would stop an install. Printed with its fix. |
worth |
Worth knowing, and not in the way. |
unknown |
Could not be checked from here. This does not mean it passed. |
A node under disk pressure is stops: its DiskPressure condition is True, or it carries the node.kubernetes.io/disk-pressure taint. Kubernetes evicts that node's pods and schedules none onto it until it has disk again, so a deployment there stops and its pods wait as Pending. The fix is to free disk on the node and wait a few minutes for the taint to lift; the pods come back by themselves. On a local VM such as Rancher Desktop's, Docker's build cache is often most of it (docker builder prune -a).
Before that, low free disk is worth. The line is 20% of the disk free, or 10 GiB where that is more. Kubernetes' default is to start evicting at 10% free for the node's own disk and 15% for images, and on one shared disk, as a local VM has, the 15% comes first. Warning at 20% leaves room for an image pull or a build before then, and the 10 GiB floor keeps that room on a small disk. Where images have a disk of their own, each disk is read on its own.
The free disk is what each node's kubelet reports, read through the API server (kubectl get --raw /api/v1/nodes/<node>/proxy/stats/summary). That needs the cluster-wide right to get nodes/proxy. Without it, free disk is unknown and says which right is missing. Disk pressure needs only the right to list nodes, and is still read. Neither needs anything run on the node.
A right the cluster refuses is stops, naming the right to ask for. kubectl auth can-i answers "no" by exiting 1, and that is read as the refusal it is rather than as unknown; unknown is left for a question kubectl could not answer.
Exit codes: 0 when nothing would stop an install, 1 when at least one check says stops.
meridian up¶
Installs the deployment's Helm chart into the cluster, waits for the dashboard, and prints the wizard's address. It runs doctor first unless you pass --no-doctor. It drives your own helm and kubectl and prints the commands it used.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--id |
<id> |
none (required) | This deployment's identifier, exactly as the platform shows it: DEP- then 26 characters from 0-9 and A-Z without I, L, O or U. Any other shape is refused before anything is installed. |
--enrolment-code |
<c> |
the value of MERIDIAN_ENROLMENT_CODE |
The one-time code the deployment enrols its key with. Required, from one or the other. Prefer the variable: a flag is written to shell history. |
--release |
<name> |
meridian |
The Helm release name. |
--chart |
<ref> |
oci://ghcr.io/open-meridian/charts/meridian-runtime |
The chart to install. |
--chart-version |
<v> |
not passed to Helm | Which version of the chart. |
-p, --params |
<file> |
none | Answer the wizard from a file instead of a browser (see below). |
--first-run-code |
<c> |
the value of MERIDIAN_FIRST_RUN_CODE |
The claim code that --params redeems. |
-f, --values |
<file> |
none | Helm-style chart values, passed straight through. May be repeated. |
--host |
<name> |
meridian.localhost |
The name the deployment is reached by through the cluster's ingress controller. A name under .localhost is plain HTTP on this machine. Any other name is reached over HTTPS, with its certificate Secrets named in a values file (-f). |
--no-ingress |
off | Reach the deployment through a port-forward that this command holds, as on a cluster with no ingress controller. | |
--development |
off | Install for development. The deployment may run plugin code while it is being written, and every page says so. plugin dev needs this. See Development deployments. |
|
--port |
<n> |
8443 |
The local port a port-forward uses. |
--timeout |
<d> |
10m |
How long Helm is given. |
--no-doctor |
off | Skip the doctor checks. |
|
--archive |
<path> |
asked | Where edge plugins' older records go: a directory on the cluster's node, such as a NAS export or a second disk mounted there, by its absolute path. It becomes the chart's pluginArchive.path. See Where older records go. |
--no-archive |
asked | No archive: records past their window stay in each plugin's storage. |
Plus the global options -n, --platform and --image.
Where older records go¶
A plugin at the edge keeps what its vendor sent it, and past the window its admin sets it archives the older records, keeps them or deletes them (see The archive). Before it installs, up asks where the archive is, unless something has said:
Where do edge plugins' older records go? Past the window a plugin's admin sets, a plugin moves them to an archive, if a deployment admin allows it one. Name a directory on the cluster's node for the archive -- a NAS export or a second disk mounted there -- or press Enter for none, and records past their window stay in each plugin's storage.
--archive <path> answers it, and --no-archive declines; given both, it is refused. Given neither, up asks at a terminal and takes none where there is no terminal to ask at. A --params file answers it with archive, a path or none, which up takes out before the wizard's fields are matched and never posts to the wizard. A values file (-f) naming pluginArchive itself, as a cloud's bucket or a claim on shared storage is, is not asked over. The archive said in two places, such as a flag and a values file, is refused before anything is installed. A path must be absolute, with no ...
Naming an archive allows no plugin one: a deployment admin allows each, with a bound or none, on the plugin's Summary. See Keep older records in the archive.
Answering the wizard from a file¶
--params posts your answers to the wizard's own endpoints, behind the same first-run code a browser redeems. The file holds the wizard's answers and no credential. Every field the wizard asks for as a password is read from an environment variable, MERIDIAN_<FIELD>, instead. A file that names such a field is refused, and the refusal names the variable to use. Field names are the wizard's own, and a misspelled one is refused against the real form.
db_host: postgres.internal
db_port: 5432
db_name: meridian
db_serving_role: meridian_app
db_migrating_role: meridian_migrate
backend: ldap
ldap_servers: ldaps://ldap.firm.internal:636
ldap_base_dn: ou=people,dc=firm,dc=internal
ldap_bind_dn: cn=meridian,ou=services,dc=firm,dc=internal
admin_group: meridian-admins
dashboard_url: https://meridian.firm.example
export MERIDIAN_ENROLMENT_CODE=…
export MERIDIAN_FIRST_RUN_CODE=…
export MERIDIAN_DB_SERVING_PASSWORD=…
export MERIDIAN_DB_MIGRATING_PASSWORD=…
export MERIDIAN_LDAP_BIND_PASSWORD=…
meridian up --id DEP-01M3GZ8K4Q7T2V9W6X5Y3R1N0P --params first-run.yaml
Exit codes:
| Code | When |
|---|---|
0 |
Installed. |
1 |
A doctor check said stops (nothing is installed), or the install failed. |
2 |
--id is missing or malformed, there is no enrolment code, --port is not a port number, or the archive is said twice or is not an absolute path. Nothing is installed. |
meridian down¶
Uninstalls the release. It asks no question, because running down is the decision.
By default the namespace is kept, and with it the database the deployment brought and the deployment's own key. Running meridian up again picks both back up.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release to uninstall. |
--delete-namespace |
off | Remove the namespace too. That removes the database the deployment brought, with all its data, which nothing backs up unless backups are on. It also removes the deployment's key, after which the platform won't give the deployment a new enrolment code until the key is revoked there. The cluster itself is never touched. | |
-n, --namespace |
<name> |
meridian |
The namespace. |
Either way, the deployment still exists on the platform. Retiring it there is what revokes its key. Not to be confused with meridian uninstall, which removes the command line itself.
Exit codes: 0 done, 1 failed, 2 given a word it does not take.
meridian upgrade-deployment¶
meridian upgrade-deployment [--release <name>] [-n <name>] [--chart <ref>] [--chart-version <v>] [-f <file> ...] [--set <key>=<value> ...] [--timeout <d>] [--yes]
meridian upgrade-deployment --enable-checksums [--release <name>] [-n <name>] [--timeout <d>] [--yes]
Moves a running deployment to a newer version of its chart, in place, with your own cluster rights. Not to be confused with meridian upgrade, which replaces the command line itself. For the whole task, and for upgrading from your own pipeline instead, see Upgrade a deployment.
It works in four steps, and stops at the step that fails.
-
Check. It changes nothing if any of these fails, and says which and what to do:
helmis 3.14 or newer, for--reset-then-reuse-values;- the cluster is reachable, and you may patch Deployments and create and delete Jobs in the namespace;
- no node is under disk pressure, read as
doctorreads it. An upgrade pulls the new version's images onto the node, which would take more of the disk it is short of, and its new pods would be scheduled nowhere. Low free disk is printed asworthand does not stop it; - the release exists and Helm holds it as
deployed. Afailedorpending-…release is refused with how to recover it; - the chart version is published, and it is the chart the release was installed from;
- it is not older than the installed version. If it is the same, it says so and exits 0.
Two more are always printed as
unknown: whether the upgrade is within the skip policy, and whether every installed plugin's runtime floor is met.unknowndoes not stop it, and does not mean it passed.If the deployment's own values set
image.tag, it says so asworth: the upgrade keeps that tag rather than moving to the chart's. -
Show and ask. It prints the release, the namespace, the version and image it is on, the version and image it moves to, and the
helm upgradeit will run, then asksUpgrade it?. Anything butyoryesis no. With no terminal to ask at, it is refused unless you pass--yes. -
Apply and wait. It runs
helm upgrade <release> <chart> --version <v> --namespace <ns> --reset-then-reuse-values --timeout <d>, with--force-conflictsunder Helm 4 and each-fand--setgiven, never--wait. Then it waits, up to--timeout, for the new revision's migration Job to complete, every Deployment and StatefulSet of the release to roll out, and every one of their pods to run the image its template names. It prints each thing it is waiting for once, when it first sees it. -
Clean up and report. It deletes the finished Jobs of this release from earlier revisions, found by the release's label, and nothing else. Old ReplicaSets are left to the chart's
revisionHistoryLimit. It then prints the versions it moved between, each component's images and readiness, every container that restarted during the upgrade with the reason Kubernetes gives and themeridian logs <component> --previousthat shows why, and what it cleaned up.Last, it names each launched plugin still on a sidecar that is not the one the components now run, since a plugin keeps the sidecar it was launched with until it is relaunched, and each plugin whose pod started during the upgrade before the new launcher was ready, since the old launcher may have launched it. For each it prints
meridian plugin stop <instance>andmeridian plugin launch <name> <version> --instance <instance>(meridian plugin dev --instance <instance>for a live one). The name comes from the plugin's image; the version is left as<version>, whichmeridian plugin listgives. It relaunches nothing itself.
It never prints the deployment's values, which hold its enrolment code. What it reads from them is image, where the deployment is reached, and where its edge plugins' archive is (pluginArchive), which --reset-then-reuse-values keeps as it keeps every value the deployment was given. The plan says so: Its archive, /mnt/nas/meridian-archive on the cluster's node, is kept: the deployment's own values carry it.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release to upgrade. |
--chart |
<ref> |
oci://ghcr.io/open-meridian/charts/meridian-runtime |
The chart the release was installed from. |
--chart-version |
<v> |
the latest published | The version to move to. It is resolved once, and that version is what is checked and applied. |
--timeout |
<d> |
10m |
How long to wait for the migration and every component, and what Helm is given. Written as Helm writes a duration: 10m, 90s, 1h30m. |
-f, --values |
<file> |
none | Chart values to change in the same approved upgrade, each file given to Helm after --reset-then-reuse-values, so it is merged over the deployment's own values as Helm merges them (mappings key by key, a key set to null removed), each file over the last. May be repeated. The files are read before anything changes, and one that cannot be read or is not a mapping stops it, as does a deployment whose own values cannot be read. The plan lists each path they set, change or remove, never a value. At the version it is on already, files that change something are an upgrade at that version, and files that change nothing leave nothing to do. |
--set |
<key>=<value> |
none | A chart value to change in the same approved upgrade, as Helm reads one (resilience.profile=ha; several split by commas; a.list[0]=v; a list as {x,y}), given to Helm after every -f. May be repeated. The plan lists each path it sets or changes, never a value, and shows the --set in the helm upgrade it will run, a value whose key names a secret, password, token, key, code or certificate shown as <withheld>. At the version it is on already, a --set that changes something is an upgrade at that version. Added in CLI 0.1.59. |
--enable-checksums |
off | Turn on data checksums in the database the deployment brought, as a step of its own: see below. Taken with --chart-version, -f, --set or --https, it is refused. Added in CLI 0.1.59. |
|
--yes |
off | Upgrade without being asked. For a script that has already read the plan. | |
-n, --namespace |
<name> |
meridian |
The namespace. |
A database moving under Patroni. Where the values give the brought database standbys, as resilience.profile=ha does, and it does not yet run under Patroni, the chart moves it in two upgrades: the first moves member 0 alone, keeping its data, and the second, with the same chart and values, adds the standbys, which clone from it. The plan says so before it asks, and the one approval covers both: it runs the second once the first has rolled out and member 0 is ready. If the first stops, it says the second was not run, and run again with the same values it finds the database under Patroni alone and runs the second. If the second stops, the first stays applied, member 0 serving. A move back to tolerant is one upgrade. Added in CLI 0.1.59. See To move to the HA profile, and back.
Exit codes:
| Code | When |
|---|---|
0 |
Upgraded, or already at that version. |
1 |
A check stopped it (nothing was changed), it was not approved, Helm refused it, the migration failed, or the wait timed out. What is still waited for is listed, and nothing is rolled back. |
Where it stops, it says what to run next, in meridian commands alone: meridian status for the release's state in Helm's own words, meridian logs migrate for a failed migration's log, meridian logs <component> --previous for why a container restarted, and meridian rollback-deployment to return to the revision it was at.
| 2 | Asked wrongly, for example a --timeout Helm would not read, a --set that is not key=value, or --enable-checksums with another change. Nothing was done. |
--enable-checksums¶
Added in CLI 0.1.59. Turns on Postgres's data checksums in the database the deployment brought, which one made by an earlier chart has off. No chart version moves with it.
- Check. It changes nothing, and says what to do, unless the release is
deployed, it brought its database, the chart holds the database's offline CronJob (<release>-meridian-runtime-database-offline, which chart 0.1.538 has), and backups are set up. Where the conductor reads checksums on already, it says so and exits 0. - Show and ask. It prints the database's size, its members and the downtime, estimated from the size: four minutes to stop and start everything, plus the size at 50 MiB a second, once for member 0 and again for each standby. Then the steps, and
Turn page checksums on?. With no terminal to ask at, it is refused unless you pass--yes. - Back up. It takes a backup as
meridian backupdoes. If it fails, nothing is changed. - Stop. It records what it stops on the database's StatefulSet, suspends the release's CronJobs, and stops every component, the launched plugins among them; under Patroni with standbys it makes member 0 the primary; then it stops the database.
- Act. It runs
pg_checksums --enableon member 0's stopped volume, in a Job made from the offline CronJob, and prints the Job's lines. - Start. Under Patroni it deletes the standbys' volumes, which Patroni makes again from member 0. It starts the database, then every component at the count it ran at, and gives each CronJob back as it was.
- Verify. It reads
data_checksumsagain, and reports it on, with the backup it took.
Where it stops, it says what that left. A failed backup, or a stop that did not finish, changes nothing, and everything is started again. A Job that failed leaves the database as it was, since pg_checksums marks it last, and everything is started again. A Job still running at --timeout leaves everything stopped while it has the volume; once it has ended, run the command again, and it takes it from there. It is refused while a restore has the deployment stopped, or while a Job of an earlier act still runs. A Job that fails or does not finish shows its cause from its log; meridian logs database-offline prints it. See To turn on page checksums.
meridian rollback-deployment¶
meridian rollback-deployment [--release <name>] [-n <name>] [--to-revision <n>] [--timeout <d>] [--yes]
Returns the deployment to an earlier revision of its release, with your own cluster rights: a release Helm holds as failed or pending-…, or an upgrade you do not want. Without --to-revision, it returns to the last revision deployed before the newest. The database is not rolled back with it.
- Check. It changes nothing if one fails:
helmruns here, the cluster is reachable, and Helm lists the release's revisions.--to-revisionmust be one of them, and not the newest. - Show and ask. It prints the revision it rolls back from, with its state and chart, and the revision it returns to, with its chart, as a new revision. Where the newest is not
deployed, it prints Helm's words for why. It says that the database stays as it is: what a newer revision's migration changed stays, and the older runtime may refuse a schema it does not know. Readmeridian logs migratefirst, and restore a backup taken before the upgrade if the data must go back too. Then it asksRoll it back?. Anything butyoryesis no. With no terminal to ask at, it is refused unless you pass--yes. - Roll back and wait. It returns the release to that revision's chart, then waits, up to
--timeout, asupgrade-deploymentdoes: for that revision's migration, if it runs one, and for every component to run on its template. A migration that fails there means the older runtime cannot read the database as the newer one left it; it names the Job andmeridian logs migrate.
For a release left pending-…, it says so before it asks: let any other Helm operation on it finish first, which it does not check.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release to roll back. |
--to-revision |
<n> |
the last revision deployed before the newest | The revision to return to, as meridian status and Helm number them. |
--timeout |
<d> |
10m |
How long to wait for every component. Written as Helm writes a duration. |
--yes |
off | Roll back without being asked. For a script that has already read what it will do. | |
-n, --namespace |
<name> |
meridian |
The namespace. |
Exit codes: 0 rolled back, and every component on its template; 1 a check stopped it (nothing was changed), it was not approved, Helm refused it, its migration failed, or the wait timed out; 2 asked wrongly, such as a word after the command, a --to-revision that is not a whole number, or a --timeout Helm would not read.
meridian backup¶
Backs the deployment up once, now. It runs the chart's backup CronJob, <release>-meridian-runtime-backup, as a Job, whether or not a schedule is set, waits for it, and prints what the Job said it wrote: names, counts, sizes and the file's SHA-256, never a value. The file, <release>-<UTC time>.meridian-backup, is written to the deployment's backup volume, sealed to backup.recipient. With --to it is also copied to a directory on this machine, and its size and SHA-256 checked against what the Job wrote. For what a backup holds and how to set the CronJob up, see Back up and restore a deployment.
It needs backups set up in the chart's values: backup.recipient, and backup.path or backup.existingClaim. Without them there is no CronJob, and it says which values to set and the meridian upgrade-deployment -f <file> that sets them.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release to back up. |
--to |
<dir> |
none | Copy the file to this directory here as well, its hash checked. The directory must exist. |
--timeout |
<d> |
10m |
How long to wait for the backup. Written as Helm writes a duration: 10m, 90s, 1h30m. |
-n, --namespace |
<name> |
meridian |
The namespace. |
Exit codes:
| Code | When |
|---|---|
0 |
Backed up, and copied when --to was given. |
1 |
Refused or failed: no backup set up for the release, --to not a directory here, the Job failed (its log is printed), the wait timed out, or the copy's size or hash differs from what the Job wrote. |
2 |
Asked wrongly: a word other than key, or a --timeout Helm would not read. Nothing was done. |
meridian backup key¶
Makes the identity backups are sealed to, an age X25519 key pair, and prints its public half (age1…) to set as backup.recipient. It needs no deployment. The identity file is written readable by you alone, and never over a file already there, since a backup sealed to the old identity would be lost with it. Keep it off the cluster: a backup opens with it and nothing else.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--to |
<file> |
./meridian-backup-identity.txt |
Where to write the identity. A file, not a directory. |
Exit codes: 0 written, 1 the file is already there or could not be written.
meridian restore¶
meridian restore <file> --identity <file> [-n <name>] [--release <name>] [--chart <ref>] [--chart-version <v>] [--timeout <d>] [--yes]
meridian restore --to-time <time> [-n <name>] [--release <name>] [--timeout <d>] [--yes]
meridian restore --to-archive-end [-n <name>] [--release <name>] [--timeout <d>] [--yes]
With --to-time or --to-archive-end, it restores the running deployment's database in place instead: see below.
Restores a backup with your own cluster rights, through kubectl and helm, into a namespace holding no deployment. It checks first, and changes nothing if a check fails:
- the file opens with the identity, and every member matches the backup's SHA-256 list;
- the namespace holds no release of that name, and nothing a deployment keeps or would take up: the backup's Secrets and ConfigMaps, the key's Secret (
<release>-meridian-runtime-key), and the claims the database the chart brings and its registry would take. One refusal lists them all. A backup volume a deleted namespace left is not a refusal: the plan says restore deletes it, within the same approval; - the chart, read at the version the backup was taken on or
--chart-version, from whatever--chartnames, is not older than the backup's, since its database may hold what only a newer runtime serves. A newer one is said: restoring is then an upgrade; - where a dump of a database somebody else runs is to be loaded,
backup.imageis set, there is a URL to load it with, and you may add a container to a pod and exec into it.
Then it shows the plan, the backup, the cluster and namespace, the chart and version, what it will make and what it will do with the database, and asks Restore it here?. Anything but y or yes is no; --yes answers for a script, and with no terminal and no --yes nothing is changed. A database somebody else runs is named by its host and database, never its credentials, with what it means: the restored deployment writes to it, so anything else running against it must be stopped first.
- It deletes the backup volume a deleted namespace left, where there is one, and the release makes it again. The volume is kept with
Retain, so the node's directory, with its backups, stays where it is. Then it makes the namespace if there is none, and the backup's Secrets and ConfigMaps, with the database's URL left empty, and the deployment's key, where the deployment made its own, as the Secret<release>-meridian-runtime-key(withkey.existingSecret, the administrator's own Secret instead). - It runs
helm installwith the backup's values that differ from the chart's defaults. - With the database the deployment brings, it starts it, makes the database and its two roles with the backed-up passwords, refuses one that already holds tables, and loads the dump. With a dump of a database somebody else runs (
backup.database: true), it adds a container ofbackup.imagebeside the conductor, which reaches the database by the cluster's own path and is gone when the conductor restarts, checks the database is empty, and loads the dump into it in one transaction as the migrating role, the URL reaching it on its standard input. A database somebody else runs that the backup does not hold is reconnected to as it was. - It counts every table's rows against the backup's counts, and stops with the URL withheld if any differs.
- It gives the components the database's URL, restarts the conductor and the dashboard, waits for first run to give up its rights, and waits for every component of the release to be ready.
Into a deployment whose brought database has standbys, as resilience.profile: ha gives it, step 3 starts the database at member 0 alone, and once the dump is loaded and counted it brings back the standbys, each cloned from member 0, before step 5; the plan and the report say so. Standbys not ready within --timeout leave the database restored on member 0 and its URL withheld. Where the values name database.caSecret, a dump of a database somebody else runs is loaded over TLS, the server verified against that CA (verify-full); a backup that does not hold that Secret is refused before anything changes, unless the namespace has it already. Added in CLI 0.1.59.
Each step is printed on stderr as it is done; on stdout, what was restored, its counts by store (config, book, street, lake, instrument, dashboard), and, after reading each launched plugin's pod for at most two minutes, only those whose image could not be pulled, to upload again with meridian plugin upload. A launched plugin not running for another reason is named too, with meridian status, which lists each plugin's readiness and restarts, and meridian logs <instance>, what it printed. See Restore a backup.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--identity |
<file> |
none (required) | The identity the backup was sealed to, which meridian backup key wrote. |
-n, --namespace |
<name> |
the backup's | Where to restore it. |
--release |
<name> |
the backup's | Must be the backup's own release: every object it restores is named for it. |
--chart |
<ref> |
oci://ghcr.io/open-meridian/charts/meridian-runtime |
The chart to install. |
--chart-version |
<v> |
the backup's | Which version of it. Without this, the version the backup was taken on, from whatever --chart names. Never older than the backup's. |
--timeout |
<d> |
15m |
How long to wait for each step. Written as Helm writes a duration. |
--yes |
off | Restore without being asked. For a script that has already read what it will do. |
Exit codes:
| Code | When |
|---|---|
0 |
Restored, and every component ready. |
1 |
Refused or failed: the file does not open with the identity, a member's hash differs, the release is not the backup's, the namespace holds the release or anything a deployment keeps, a left-behind backup volume could not be deleted, the chart is older than the backup's, it was not approved, the database holds tables already, a table's count differs, Helm refused, or a wait timed out. Whatever was made is left, and the database's URL stays withheld if the database was not checked. |
2 |
Asked wrongly: no backup file, or more than one, no --identity, or a --timeout Helm would not read. Nothing was done. |
meridian restore --to-time¶
Added in CLI 0.1.59. Restores the running deployment's brought database, in place, from its WAL archive (backup.walArchive) to a time: the newest base backup before it on the timeline that held it restored, the log replayed along that timeline to it, and the database promoted there on a timeline of its own. It takes no backup file and no identity. See Restore to a point in time.
The timeline is chosen, never asked (changed in CLI 0.1.60). Each restore starts a timeline of its own, so a time before an earlier restore's lies on an earlier timeline. The restore reads the archive's timeline history, recovers along the timeline that held the time, from the newest base backup on that timeline's history, and the report says which: It recovered along timeline 1, the one that held 2026-11-09T13:59:27Z. There is no option to choose it. On an older chart, which restores along the archive's newest timeline only, it says so before anything stops.
- Check. It changes nothing, and says what to do, if the time is after now, the release is not
deployed, the deployment brought no database, it keeps no WAL archive (with how to turn it on), the chart has no offline CronJob, or a Job of an earlier act still runs. The archiver's newest attempt failing isworth. - Show and ask. It prints the database, the time, the archive's last segment, and the steps, and asks
Restore it to that time?. With no terminal to ask at, it is refused unless you pass--yes. - Stop. It records what it stops on the database's StatefulSet, suspends the release's CronJobs, stops every component, the launched plugins among them, has the newest WAL archived, and stops the database.
- Restore. It runs the restore on member 0's volume, in a Job made from
<release>-meridian-runtime-database-offline, and prints its lines. - Start. Under Patroni it deletes the standbys' volumes, which Patroni makes again from member 0. It starts the database, then every component at the count it ran at, and gives each CronJob back as it was.
A time outside the archive (before its oldest base backup ended, at or past its last commit, or while the database was stopped between two timelines) is refused in the restore's own words before the database's volume is touched, naming the archive's first and last times, and saying the volume is as it was. Everything stays stopped and the CronJobs suspended, and the hint names the first and last times back, with --to-archive-end for the end itself. Run again with a time inside the archive, it takes it from there, as it does after any restore that did not finish. While it has the deployment stopped, meridian status says so and names the command that resumes it.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--to-time |
<time> |
none | The time, RFC 3339 with its offset: 2026-11-08T14:03:07Z or 2026-11-08T09:03:07-05:00, a fraction of a second up to six digits or none. |
--release |
<name> |
meridian |
The Helm release. |
--timeout |
<d> |
30m |
How long to wait for each step. Written as Helm writes a duration. |
--yes |
off | Restore without being asked. For a script that has already read what it will do. | |
-n, --namespace |
<name> |
meridian |
The namespace. |
Exit codes: 0 restored, and everything started again; 1 a check stopped it (nothing was changed), it was not approved, or the restore or a wait failed, said with what it left and what to run; 2 asked wrongly: a time not in RFC 3339, a backup file or --identity beside --to-time, or a --timeout Helm would not read.
meridian restore --to-archive-end¶
Added in CLI 0.1.60. The same as --to-time, with the same checks and steps, to the last commit the archive holds, along its newest timeline: where the deployment was when it stopped. It asks Restore it to the archive's end?, and says it recovered along the archive's newest timeline. It is refused without backup.walArchive, saying how to turn it on, and on an older chart, before anything stops. A stop it leaves is resumed by meridian restore --to-archive-end, as status says. Naming both --to-time and --to-archive-end is asked wrongly (exit 2). Its flags and exit codes are --to-time's.
meridian status¶
How the deployment is running, read only. First the Release: its revision, its state, its chart and app version, and when it was last changed. Where Helm holds it as anything but deployed, it adds Helm's own words for why, and the meridian rollback-deployment that returns it to its last deployed revision.
Then Components: each workload the chart runs, named by its meridian.dev/component label or by its pods' selector, so the database, the plugin registry and the broker are listed too. Then Plugins: each launched plugin, by its instance. For each component and plugin:
- the image it runs (for a plugin, the plugin's own image, not its sidecar's);
- how many of its pods are ready, of how many wanted;
- how often they restarted, and why and when the last time (
OOMKilled,Error); - each pod's own memory against the limit each pod has, as its working set, the way Kubernetes counts it: the pod's usage less its inactive file cache, read from each node's cAdvisor, or from the metrics API (metrics-server) where that read is refused, which it says;
--jsonnames where the figure came from inmemory_read. Without either it says memory in use is not known.
Then Database (added in CLI 0.1.59): the database the deployment brought, its members, whether it runs under Patroni and whether it keeps a WAL archive; each member as primary, standby or alone, ready or not, and how far a standby is behind the primary; a member whose node is not ready, which waits for it and is never force-deleted; whether data checksums and full_page_writes are on, as the conductor reads them, with the doctor hint where either is off; the WAL archiver's last segment and whether it is failing; the replica comparison's last run, with standbys; and whether TLS inside the cluster is on. For a database somebody else runs it says so, with its settings and address.
Then Integrity and members (added in CLI 0.1.59): each store member's own answer, its mode (writer or standby), whether it is ready, and its lag, and from CLI 0.1.60 its zone and the partitions it owns or follows, a long list cut on its line (and 2 more (--json lists them)) and whole in --json; and, under the writer, when the store last read its own records again (<store>.scrub.intervalDays), how many it checked, and any finding not yet answered.
While a restore to a time or upgrade-deployment --enable-checksums has the deployment stopped, a Stopped by block comes first (added in CLI 0.1.59): which command, since when, the CronJobs it suspended, meridian logs database-offline, which says where its act is, and the command that takes it from there and starts everything again.
Then Book health: per account in the book of record, how many of its journal's entries the book replays to answer it since the account's last snapshot, and when that snapshot was taken. Book health is the deployment's own answer, read through the session meridian connect keeps (--deployment names one); without a session it says why it was not read, and the rest is still shown.
Release: meridian revision 8, deployed, chart meridian-runtime-0.1.… (app …), updated …
Components:
bor 1/1 ready no restarts memory 201 MiB of 512 MiB (39%) ghcr.io/open-meridian/meridian-runtime:…
conductor 1/1 ready 1 restart(s), the last OOMKilled at … memory 96 MiB of 256 MiB (37%) ghcr.io/…
...
Database:
meridian-meridian-runtime-database 3 member(s), under Patroni, WAL archive on
meridian-meridian-runtime-database-0 primary ready
meridian-meridian-runtime-database-1 standby ready 0 B behind the primary
meridian-meridian-runtime-database-2 standby ready 0 B behind the primary
page checksums on, full_page_writes on
WAL archive: last segment at … (52s ago); not failing
replica comparison: no run kept yet
internal TLS on
Integrity and members (each store member's probe):
meridian-meridian-runtime-bor-5cdc8c875b-8f8qm writer, ready, lag 0 ms, zone zone-a, partitions control
bor record: not scrubbed yet; no findings
meridian-meridian-runtime-bor-5cdc8c875b-rdx4m standby, ready, lag 0 ms, zone zone-b, partitions control
...
Plugins:
snaptrade 1/1 ready no restarts memory 88 MiB of 512 MiB (17%) …
Book health:
ACC-01JB8Z3K6Q replays 120 of its 48210 entries, since its snapshot of … (3h ago)
The components are read through your own kubectl and the release through your own helm, as doctor and backup read the cluster; nothing is changed, and you never run kubectl or helm yourself. A memory limit is set in the chart's values: change it with meridian upgrade-deployment -f.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release. |
--json |
off | One JSON object on stdout: release, with release, revision, status, chart, app_version, updated and description, or not_read with why; components and plugins, each with component, kind, images, ready, wanted, restarts, last_restart_reason, last_restart_at, memory_bytes (the busiest pod's), memory_limit_bytes (each pod's limit) and pods, each pod's pod and memory_bytes, and a plugin also instance_id, plugin_image and launched; metrics; and book_health, each account's account_id, journal_entries, entries_since_snapshot and last_snapshot_at_ns, or not_read with why. Added in CLI 0.1.59, beside every key it had: database, with statefulset, patroni, wal_archive, wanted and members, each member's pod, role, ready, node, bytes_behind_primary and waits_for_node; database_settings, with data_checksums, full_page_writes and primary; wal_archiver, with last_archived_at, seconds_since, failed and failing; replica_comparison, the last run's answer; internal_tls; stopped_by, with act, command, since, resumed_by, database_members, components and cronjobs_suspended, or null; and probes, each store member's component, pod, member, integrity and not_read. Added in CLI 0.1.60: each probe's member names its zone and its partitions, whole. |
|
--profile |
off | The resilience profile alone: see below. |
Exit codes: 0 read; 1 no deployment of that release in the namespace, or kubectl failed; 2 asked wrongly.
meridian status --profile¶
Added in CLI 0.1.60. The resilience profile as the deployment computes it from its configuration as it is, never a preset's name: its level (Desktop, Single node, Multi-node or Multi-zone) and what the next level lacks, each change with its cost; each failure class and each tier, met, met by restart or not met, with its reason and the change that would meet it and its cost; and never blind per live data type (a second source, standby pods, age and source shown). It changes nothing: a change it names is made with meridian upgrade-deployment --set, or at the dashboard's Plugins and Data sources. See Read your profile.
It reads the deployment's own answer through the session meridian connect keeps, a deployment admin's; --json prints that answer as it is (level, next_level, next_level_changes, failures, tiers, never_blind and computed_at_ns), as dashboard__read_resilience_profile answers it.
Exit codes: 0 read; 1 not read: no session, a person who is not a deployment admin, or an older core that does not answer it, each said.
meridian logs¶
What a component's pods printed, each line under its pod's name, every container's. <component> is one of:
- a component
meridian statuslists, such asbororconductor; - a plugin, by its instance, as
meridian statuslists it under Plugins; - one of the release's Jobs, such as
migrate,key,first-run,backupordatabase-offline(the act of a restore to a time or of--enable-checksums): the newest run of it.
Any other is refused, naming the components and plugins there are. It reads through your own kubectl, and changes nothing.
meridian logs bor --since 10m
meridian logs bor --previous # what it printed before its last restart
meridian logs migrate # the newest migration Job's log
meridian logs snaptrade # a plugin, by its instance
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release. |
--since |
<span> |
all | Only what was printed in the last 30s, 10m, 2h or 1d. |
--previous |
off | What each container printed before its last restart. Where none has restarted, it says so. |
Exit codes: 0 printed; 1 not a component of the release, nothing from before a restart, or kubectl failed; 2 asked wrongly.
meridian connect¶
Signs you in to a deployment's dashboard in your browser, however that deployment signs people in, and asks you to let the CLI on this computer act as you there: a delegation, for up to 90 days. It never takes a password.
With no address it signs in to https://meridian.localhost, the deployment meridian up installs on this machine by default. Give an address for any other.
<address> is the dashboard's address alone, with no path:
https://<host>;http://only for this machine:127.0.0.1,[::1],localhost, or a name under.localhostsuch ashttp://meridian.localhost.
Plain HTTP to any other machine is refused, because it would send your sign-in and your tokens in the clear.
It does this:
- Registers this computer with the dashboard, the first time it connects there, as a client named
meridian on <host>, after this computer's host name. Each computer is its own client, listed and revoked apart. Registering grants nothing. - Signs you in. It prints the sign-in link and opens it in your browser, then waits up to 5 minutes for the sign-in to come back to a loopback address on this machine. The sign-in is always made afresh, never taken from a browser already signed in.
-
Asks you. The dashboard then shows Allow a client to act as you: the client's name, that its codes go to this computer, and:
- What it may do: Everything you hold, as that changes, or Only what is ticked below, ticked from what you hold: each plugin at each level you hold on it (Manage, Open, View), the account groups your permissions name, and, if you hold it, Deployment admin: the deployment's own settings, accounts and plugins, but never who holds access.
- Until: 7, 30 or 90 days.
For the CLI the page starts at everything and 90 days. Choose Allow, or Don't allow. Allow it only if you started it yourself, just now.
-
Keeps the delegation. It exchanges the one-time code for a ten-minute access token and a single-use refresh token, keeps them, and prints who you are connected as and when the delegation lapses.
The delegation¶
Every command presents the access token. When it has under a minute left, the command refreshes it first, and when the deployment refuses it as expired, the command refreshes it once and asks again. Refreshing spends the refresh token and keeps the next pair. Nothing asks for a browser again until the delegation lapses or is revoked, so plugin dev can watch overnight.
A refresh token is single use, and the deployment revokes the delegation when a spent one is presented, because that only happens when somebody else holds it too. So refreshing takes an exclusive lock on that deployment's sessions file and reads it again under the lock: a second command finds the pair the first wrote, and plugin dev in one terminal and plugin list in another never revoke your CLI.
What a command may reach is what you hold at that moment, cut to what the delegation covers. plugin upload, list, launch, stop, dev, logs and events need it to cover the deployment admin's capabilities, and a delegation that does not is refused, saying to connect again to widen it; plugin open needs it to cover the level it opens at. Everything the CLI does is recorded as yours, through it.
Within a week of the lapse, every command says when, once:
Your delegation to this computer at https://meridian.firm.example lapses at <time>. `meridian connect https://meridian.firm.example` renews it.
The dashboard's home says so too. Connecting again renews it: this computer keeps its registration, and the page says you delegated to it before. You see the delegation, and can revoke it, in Connected clients on the dashboard; see Access.
It ends when you run meridian sign-out, when you or a deployment admin revoke it from Connected clients, when it lapses, or when the account's password is reset. The next command then exits 3, saying why and naming the meridian connect to run.
This computer holds one delegation per deployment. Each is kept in a file readable only by you, in $XDG_CONFIG_HOME/meridian/sessions, %APPDATA%\meridian\sessions, or ~/.config/meridian/sessions, whichever is found first. Connecting again replaces what the file holds.
Exit codes: 0 connected; 1 the sign-in was declined, timed out, or failed, the dashboard does not take delegations, or the delegation could not be kept, in which case it is revoked; 2 no address, or an address of the wrong form.
meridian sign-out¶
Revokes this computer's delegation at the deployment and forgets it here. With no address, it signs out of the one deployment you are connected to. If you are connected to more than one, it lists the commands to run and exits 2.
If the deployment can't be reached, the delegation is still forgotten here. It says so, with when the delegation lapses: revoke it from Connected clients on the dashboard, or it lapses then.
Exit codes: 0 signed out, or not connected to begin with; 1 the sessions file could not be removed; 2 a malformed address, more than one address, or no address when connected to several.
meridian plugin new¶
meridian plugin new <name> [--into <dir>] [--role connectivity|custody|data|operations|pricing|reporting]
CLI 0.1.60
CLI 0.1.60 writes SDK 0.35.0's templates: each role the plugin holds is
one class, meridian.roles.<role>.Role, and the plugin enters through
meridian.run(declaration=..., roles=[...], ...) with its classes
registered there. Move a new plugin to SDK 0.36.0 with
meridian plugin migrate. Every template carries a
docs/ folder, its whole declaration named in [tool.meridian], and a
CI workflow checking with the meridian release that wrote it
(MERIDIAN_VERSION).
Writes a working plugin to start from: the Python SDK's reference plugin, renamed to <name>, or with --role that role's template. It needs no network, because the template is compiled into the binary. It writes the plugin's code and its pages, each a view function and a Jinja2 template declared with the levels it serves (a Setup page under Manage, at admin, and an Accounts page under Open and View, at write and read), its declaration (src/<module>/declaration.py, which [tool.meridian] names), its documentation (docs/overview.md, setup.md, troubleshooting.md, terms.md and pages.md, each a short prompt to replace; see Write a plugin's documentation), its tests (tests/test_page.py), a Dockerfile, pyproject.toml, .dockerignore, .gitignore, README.md, AGENTS.md, CLAUDE.md, a develop-live skill for Claude Code, a CI workflow that runs meridian plugin check --run-tests (.github/workflows/check.yaml), and a pre-push hook, hooks/pre-push, then prints the next steps. Where git has no SSH key to sign release tags with, it then offers meridian plugin key setup at a terminal, asking Set up a signing key now?, and only says so where there is no terminal. The hook is CI-first: a branch's push runs nothing, its pull request's CI being the gate; a tag push runs nothing either, since plugin release checked main's CI before signing it; and a push straight to main runs meridian plugin check --run-tests first. Turn it on in a fresh clone with git config core.hooksPath hooks. The plugin pins SDK 0.35.0, and needs a deployment on chart 0.1.408. Its AGENTS.md teaches a coding agent the kinds of raw record a plugin at the edge declares and their windows, and that a custody plugin keeps each activity's raw record at least as long as the history it reported; how a data plugin records trades and quotes; that work the plugin does on its own changes something only by calling one of its acts as its role's service account (meridian.runs_as), each such act naming its check tool, and names the declared algorithm it runs (meridian.runs_as(role, algorithm_id=...)); and that a date is worked out in core (compute_date, read_calendar), never by hand; and that a change raises version and adds its CHANGELOG.md entry, ## X.Y.Z with no date, in its own pull request, and a version is then published to a registry with meridian plugin release. Which SDK each earlier CLI pinned is in Moving a plugin to a new release. See Your first plugin.
<name> must be lowercase letters, digits and single hyphens, starting with a letter. It becomes the package name, and, with hyphens as underscores, the module name.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--into |
<dir> |
./<name> |
Where to write it. Never somewhere that already exists. |
--role |
connectivity, custody, data, operations, pricing or reporting |
none | Write that role's template instead, from the SDK's templates/, a whole plugin holding the role on the role's common part (meridian.roles), passing role-pages, with its tests running the role's suite where it has one; the custody, data and reporting templates' suite tests map the settlement calendars' two cases, and the reporting template's its suite's three order-path cases too. A role with no template of its own is refused, naming those that have one; the reference plugin takes any role in its pyproject.toml. |
--role connectivity writes a plugin that places what its ems released at a venue, in the account its ems mapped, and reports what the venue did, every fill into the book. Its whole work is the SDK's Trader (meridian.roles.connectivity), over a stand-in venue held in memory (venue.py), an adapter reading the venue's words into the SDK's: replace it with your venue's. Its one page is the connectivity role's Trading page under Manage, with its read tool, read_trading, and Enable trading on a read-only account (enable_trading, checked by check_enable_trading). It declares one kind of raw record, trading, kept seven years by default, so a restart sends nothing twice. Its tests (tests/test_trading.py) use fakes only, through the SDK's testing.Rig, and do not run the connectivity suite: it keeps every rule of plugin check, and under --verified, role-suite asks for a test that runs the suite. See Route orders through a connectivity plugin.
--role data writes a plugin that puts a stand-in vendor's daily closes and bars into the lake: its catalogue declared from code, every price parsed from the vendor's text as a Decimal, every subject and venue resolved before a row names it, wants answered, and its Connection and Datasets pages under Manage, each with its read tool, read_connection and read_datasets. Replace the vendor (vendor.py) with your own. Its tests run the data suite, marking the suite's cases about trades and quotes, which its daily dataset never publishes, not presented, with why. Its pages are the data role's common pages (meridian.roles.data), and it reads its datasets' licences, so Datasets says whether each is licensed. See Write a data plugin against its suite.
--role custody and --role operations write a plugin holding that role with its common pages: for custody, Connections and Account links under Manage and Statements under Open and View, a stand-in custodian to replace; for operations, Breaks, Reconciliations and Opening balances under Open and View, its opening balance's confirm naming its check (checked_by=), so it can be prepared. See Using a custody plugin and Using an operations plugin.
--role reporting writes a plugin that shows the positions in its account scope valued at the last close from the lake, on a Closes page under Open and View and a Datasets page under Manage, and records nothing. It resolves its reporting currency by its ISO 4217 code, and its Datasets page and figures are the reporting role's common ones. Its tests run the reporting suite. See Value the book.
--role pricing writes a plugin that answers the analytics component's batches for the pricing models its version declares, through the SDK's Pricer (meridian.roles.pricing): each batch answered whole, from exactly the market objects each unit carries, and the batches still waiting read after a restart from the last sequence answered, kept in memory, since pricing is no edge role and declares no storage. Its tests run the pricing suite. See the pricing role.
Exit codes: 0 written, 1 refused (bad name, the directory exists, or a role with no template), 2 asked wrongly.
meridian plugin check¶
CLI 0.1.60
CLI 0.1.60 reads the current roles and suites: the data suite's
records-a-derived-series and records-a-pools-factor can be named in
not_presented, and so can every read the suites mark with its about
(datasets, orders, trading instructions, replacements, templates, event
groups and the rest), reads-its-datasets-licences among them, so a
plugin whose work never makes one names it, with why. role-pages
counts the pages, routes and tools an SDK helper declares for the
plugin, such as meridian.roles.custody.account_pages and
connectivity's trading_pages, connection_pages and
raw_records_page.
Holds the plugin in --dir to the framework's rules: the rules every plugin is built to, so that plugins written by different people and different coding agents look and behave alike, and a change to what plugins call can be applied to all of them. It needs no deployment and no session, changes nothing, and reads nothing outside the directory. Run it while you work, and in the plugin's own CI.
Each failure names the rule, the file and line, and what to write instead, so that a coding agent can fix it without asking. The rules are compiled into the binary: the check a plugin meets is the one of the meridian that runs it.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--dir |
<dir> |
. |
The plugin's directory. |
--run-tests |
off | Also run the plugin's tests with pytest, in the project's own environment: uv run --frozen for a project with a uv.lock and uv on the PATH, else the Python of its .venv or venv, else python3; a run that cannot import the plugin or the SDK says which module is missing, which must have the plugin and pytest installed. No cache or bytecode is written. |
|
--verified |
off | Hold it as a verified plugin is: every route that changes something a tool for agents, none kept from them (tools-cover-routes), each role it holds with a suite run in its tests (role-suite: custody, data, connectivity and pricing), and each role's common part kept and every act naming its check tool (role-pages). |
|
--json |
off | One JSON object on stdout instead of the report. |
The rules¶
| Rule | Holds | Fails on |
|---|---|---|
template-shape |
The project keeps the template's shape. | No pyproject.toml, or one with no plugin name or version; the SDK not pinned exactly (open-meridian==<version>); no Dockerfile, or one not built on ghcr.io/open-meridian/plugin-python:<the pinned version>; no src/<module>/__main__.py; no entry calling meridian.run(...), meridian.run_async(...) or meridian.connect(...) (or run, run_async or connect imported from meridian), read in code, so a docstring naming one does not count; no AGENTS.md; a CLAUDE.md that does not begin from @AGENTS.md; a .dockerignore that does not name AGENTS.md and .meridian. |
tool-meridian |
[tool.meridian] names roles from the fixed list, and no tags. |
No [tool.meridian]; a role not in the deployment's list (compliance, confirmation, connectivity, custody, data, ems, oms, operations, portfolio, pricing, reference, reporting, servicing, settlement, signal); tags, empty or not; an interface that is not true or false. |
roles-registered |
Every role [tool.meridian] declares has a class of it registered in roles=[...]. |
A declared role with no class of it registered, named at the entry's line. A class registered for a role the plugin does not declare is a warning: the SDK does not start it. A registered object is traced to its role through its imports, assignments and the plugin's own classes and bases, across modules; one the check cannot trace fails nothing. A file under e2e/ is judged by the roles it registers itself. A plugin pinning an SDK before 0.35 that connects without roles= keeps every rule as before. |
edge-storage |
Only a plugin at the edge asks for storage in its declaration. | A plugin holding no edge role asking for storage. See Keep what your custody plugin converts. |
role-suite |
A verified plugin holding a role with a suite runs the suite in its tests. | With --verified: a plugin holding custody, data, connectivity or pricing with no test running that role's suite, which --run-tests then runs. Without --verified it is reported skipped: a plugin not passing its role's suite is not verified for it, and breaks no rule, so the reference plugin given custody keeps every rule. Only the custody, data, connectivity and pricing suites are held: a plugin holding reporting, signal, oms, ems, portfolio, operations or compliance is not checked for its role's suite, which its own tests may still run. The connectivity suite's cases include sending nothing new on a pause and cancelling at the venue on a stop, which no connectivity plugin may name not presented. It also holds the test to the outcome --run-tests then trusts (changed in CLI 0.1.43). Any skip or expected failure where a suite is run fails it at its line: a marker (pytest.mark.xfail, strict or not, skip, skipif, unittest's skip and expectedFailure), a call (pytest.xfail(), pytest.skip(), importorskip) or a pytestmark, in the module running it or a conftest.py over it, since only the interface's closed_list, about and about_field excuse a case. And, since the SDK's runner reports rather than raises, a collected test (test_* in a test_*.py) binds the run and asserts it passed in the very next statement, report = run("<role>", producers, ...) then assert report.passed, report.failures, under no try, with, if or loop: a test asserting a count or one case, catching the failure, or changing the report first fails it at the suite's first run. Tests that expect the suite to fail may stand beside that one. See Keep what your custody plugin converts and Write a data plugin against its suite. |
role-pages |
A plugin holding a role with a common part keeps its common pages, tools and settings, in the role's order, and every act names its check tool. | With --verified, for each of data, custody, operations and reporting the plugin holds: a common page missing, retitled, at another path or level, or after a page of the plugin's own; a common page's read tool missing or renamed, or its record (meridian.roles.<role>'s <Page>Read, or the plugin's own) lacking a common field; a common setting missing or out of order (environment first, sample_data last); a page, setting or figure of the plugin's own taking a common title or label; and an act, a route that changes something and is a tool, naming no check tool (checked_by=), or one no read route declares; where a tool replaces the route (@pages.tool(replaces=...)), the tool is held to it instead, unless it or the route it replaces reads. Without --verified each is said as advice and fails nothing. A page, route or tool one of the SDK's helpers declares counts as the plugin's own, placed where the helper is called, however the module imports it: meridian.roles.custody.account_pages declares Account links, check_link_account, link_account, Statements and History. A missing common page or act names the helper that declares it. Changed in CLI 0.1.51: connectivity's page helpers count too. Candidates (custody's, operations' and reporting's items from one plugin) are not held. See Using a data plugin and The role's common pages. |
runs-as-checked |
Every act the plugin's own work calls through runs_as names its check tool. |
With or without --verified: in the plugin's Python, its tests not, a call in the block of async with meridian.runs_as(...) as act: (runs_as however it is reached: meridian.runs_as, imported, imported as another name, or a name the module binds to it) to one of the plugin's acts, by its tool's name as written or through a constant bound at the top of a module, that declares no check tool (checked_by=), at the call's file and line, naming the act and where it is declared, since a service account set to prepare only is refused such an act at run time. A tool named some way the text cannot read, or none of the plugin's acts, is passed over. See A service account runs the plugin's autonomous work. |
kit-linked |
Every page links the kit from /.meridian/ui/, and the plugin holds no copy of it. |
A file that writes a document (<!doctype html>, <html> or <head>) with no link to /.meridian/ui/…/meridian.css; a file named meridian.css or meridian.js in the plugin. |
no-raw-colour |
No raw colour in a page or its styles. | A hex colour where a colour goes, rgb(), rgba(), hsl(), hsla(), hwb(), oklch() or oklab(), or a CSS colour name as the value of a colour property (color, background, border, fill, stroke and the like), a colour attribute (fill="…") or a script's .style.…Color. A fallback in var(--x, #fff) is a raw colour too. |
own-origin |
A page loads nothing from another origin. | An absolute or protocol-relative address (https://…, //…, wss://…) as a script's, stylesheet's, image's or frame's source, in @import or url(), or given to fetch, EventSource, WebSocket or import. A link people follow (<a href>) is not loading. |
tools-cover-routes |
Every route that changes something is a tool for agents, or says why not. | A route that changes something with no params= record and no tool=False with why=. See Offer your plugin's pages to agents. |
roles-declared |
On a plugin holding several roles, every page, route and setting names its roles, and a changing route sends only its roles' commands. | Only on a plugin whose [tool.meridian] names several roles: a page, route or setting naming no roles=, or a role the plugin does not hold, a tool held to its route's roles unless it names its own; a route that changes something (any method but GET and HEAD) whose view sends a command none of its roles holds, by meridian-schema's published roles.json, at the line of the call, naming the roles that hold it. roles= is read as written or through a constant bound at the top of a module. A plugin holding one role, or none, names none, and the rule holds. See Roles. |
window-settings |
No setting of the plugin's own takes a declared kind's window setting's name. | A setting named <kind>_window_days or <kind>_past_window for a kind the plugin declares with RecordKind(...), at its line, as written or through a constant bound at the top of a module. The SDK declares both settings for every kind, and refuses a plugin declaring either itself. See The window settings. |
settings-declared |
Settings are declared to the SDK, not read from the environment. | os.environ, os.getenv, getenv( or environ[…]. One name is allowed: a variable ending _PAGE_PORT, where the page listens on loopback for its sidecar, as the template's does. That is the plugin's own wiring, which nobody configures. |
secrets-kept |
No secret setting's value is logged or put in a page. | For each meridian.Setting(…, secret=True), by its name or the constant that names it: its value (values[NAME], .get(NAME), .name, {name}) in a logging call, print, a raise, or a statement that writes HTML. Its name alone, as in "waiting for broker_api_key", is not its value. |
through-the-sdk |
The deployment is reached only through the SDK. | Importing nats, psycopg, psycopg2, asyncpg, sqlalchemy, pg8000, aiopg, grpc, kubernetes, or the sidecar's raw *_pb2_grpc stubs; naming nats://, postgres://, a cluster service (*.svc, *.svc.cluster.local), meridian.localhost, a dashboard /terminal/ path, or MERIDIAN_SIDECAR_ADDRESS. |
replaced-ids-followed |
No instrument ID kept in the code is written after its record is replaced. A warning: it fails nothing, since the deployment accepts a replaced ID on every write, forever. Added in CLI 0.1.45. | Warns, with or without --verified, on a string in the plugin's Python, its tests not, holding an INS- or LCL- ID and nothing else, at its file and line: a provisional LCL- record is replaced once the platform answers, and an INS- record when the platform merges it. Resolve the instrument from an identifier it states (resolve_identifier), or follow the kept ID to the record it became before writing it, ids.current("<id>") with meridian.CurrentIdMap. An ID followed where it is written is not kept. With --json, each is in warnings. |
documentation-sections |
The plugin's documentation is in docs/, and its declaration carries it. Added in CLI 0.1.46. |
No docs/overview.md or docs/setup.md; with --verified, no docs/troubleshooting.md or docs/terms.md either; a docs/ folder whose sections no declaration carries: [tool.meridian] naming no declaration, or one naming no documentation=. Advice, failing nothing: troubleshooting.md or terms.md missing without --verified; a file in docs/ that is not a section, which is not uploaded. On a plugin pinning an SDK before 0.29.0, every finding is advice. See Write a plugin's documentation. |
documentation-safe |
Each section is plain Markdown the Documentation tab shows as written. Added in CLI 0.1.46. | A section that is empty, not UTF-8, or over 64 KiB, or sections over 256 KiB in all; raw HTML; an image; a javascript:, vbscript:, data: or file: link; a link whose text names one host and whose target another; a character that hides text; and text the screen refuses, by the rules core and the platform name: override, acting for the reader, a claim of endorsement, verification or regulatory readiness, posing as Open Meridian, exfiltration, concealment, and claiming authority, which the deployment's upload and the platform's build refuse too. Advice, failing nothing: a level-two heading's text past one screen of the tab (200 words); a link the tab shows as plain text; and text matching the screen's shape rules (chat markup, answer-shaped text, instruction words beyond the interface's own allocation, trading, settlement, execution and handling instructions, text addressed to the reader, a tool's name, a role marker such as system:, talk of an agent's prompt), which the deployment and the platform show to people marked Screened and withhold from agents, never refusing it. |
tests-exist |
The plugin has tests. | No test_*.py or *_test.py with a def test_… in it. |
tests-pass |
Its tests pass. | With --run-tests: pytest exits non-zero, collects nothing, or is not installed. Without it the rule is reported as not run, and does not fail. |
The page rules read HTML, CSS, SVG, script and templates, and the Python that renders a page, so they hold for a plugin in any language. The rules about the plugin's code read Python, the one SDK there is. Tests (tests/, test_*.py, conftest.py) are not held to the page and code rules: a test may name a colour to assert it is absent. Comments are not read, so a colour in a comment is on no page.
Nothing a checkout, a build, a virtual environment or an agent leaves beside the plugin is read, such as .git, .venv, build, dist and .claude. Nor is .e2e, where a plugin's e2e copies core's plugin harness out of its image: core's code, not the plugin's, which reads its own environment.
The rules match what can be decided from the text: the obvious forms, not every form. A secret copied into another variable before it is logged, or a colour built from parts in a script, gets past them. They are the floor, not the review.
What it cannot check¶
These stay advice, in the template's AGENTS.md, because deciding them takes judgement:
- which of the kit's components or classes fits what the page shows;
- the page's layout, and whether it draws only its content, with no header bar, navigation, sign-in or theme switch of its own;
- custom properties the kit does not define, other than the page's own layout ones (the check does not know the kit's list);
- that prices and quantities reach the page as decimal strings and are never parsed as floats;
- names: of the plugin, its settings, its pages and its tests;
- whether the tests test what matters.
Output¶
In text, one line per rule, and under a failed one each place it failed and what to write instead:
meridian plugin check: ., by the rules of meridian 0.1.15
ok template-shape the project keeps the template's shape
ok tool-meridian [tool.meridian] names roles from the fixed list, and no tags
ok kit-linked every page links the kit from /.meridian/ui/, and holds no copy of it
FAIL no-raw-colour no raw colour: no hex, rgb(), hsl() or colour name in a page or its styles
src/my_plugin/page.py:41: `#c0ffee` is a raw colour, which no scheme can change
instead: the kit's custom property for what the colour means, as var(--name): …
ok own-origin a page loads nothing from another origin
ok settings-declared settings are declared to the SDK, not read from the environment
ok secrets-kept no secret setting's value is logged or put in a page
ok through-the-sdk the deployment is reached only through the SDK
ok tests-exist the plugin has tests
-- tests-pass its tests pass: --run-tests runs them
1 of 10 rules failed, 1 place(s) in all. Fix each as it says, then check again.
With --json, one object. line is null when the failure is a whole file, or a file that is missing:
{
"dir": ".",
"meridian": "0.1.15",
"passed": false,
"rules": [{"rule": "no-raw-colour", "holds": "no raw colour: …", "outcome": "failed"}, …],
"failures": [
{"rule": "no-raw-colour", "file": "src/my_plugin/page.py", "line": 41,
"found": "`#c0ffee` is a raw colour, which no scheme can change",
"instead": "the kit's custom property for what the colour means, as var(--name): …"}
]
}
outcome is passed, failed, or skipped for tests-pass without --run-tests, and for role-suite without --verified, its line saying --verified holds it.
Advice fails nothing: it is printed after the rules, each with its rule, file and line and what to write instead, and listed under advice in --json, in the same shape as failures. A missing troubleshooting.md or terms.md, a file in docs/ that is not uploaded, a heading's text past one screen of the Documentation tab, a link shown as plain text, and text matching the screen's shape rules are advice; so is role-pages without --verified.
A freshly scaffolded plugin
The plugin meridian plugin new writes keeps every rule, with its own tests and a CI workflow (.github/workflows/check.yaml) that runs meridian plugin check --run-tests.
Exit codes: 0 every rule holds; 1 at least one does not; 2 asked wrongly, or --dir is not a directory.
meridian plugin migrate¶
meridian plugin migrate [--to <version>] [--dir <dir>] [--image <ref>] [--force] [--run-tests] [--json]
Moves the plugin in --dir to a newer release of the Python SDK, open-meridian. Every release of the SDK that changes what a plugin calls carries a migration from the release before it: code that rewrites the plugin to the new form, and a record of what it cannot rewrite and what a person or a coding agent must then do. Every other release carries one that only moves the pins. So the steps from any recorded release to the newest exist, and plugin migrate runs them in order. The first recorded step is from 0.5.0.
It needs no deployment and no session. It needs docker, and, unless you name both --to and --image, the Python package index, which it asks for the SDK's releases; for a base pinned by digest, ghcr.io too. It needs no Python on your machine.
In order, it:
- finds the plugin's two pins,
open-meridian==<version>inpyproject.tomlandghcr.io/open-meridian/plugin-python:<version>in theDockerfile, which must name the same release; - refuses, changing nothing, if the plugin's directory has changes git does not hold yet, or is not in a git repository at all, unless you pass
--force. A migration rewrites files, and git is how you see what it did and take it back; - picks the target:
--to, or the latest release. It never goes backwards, and a--tothat is not released is refused unless you name the image to use with--image; - moves both pins to the target, and
.github/workflows/check.yaml'sMERIDIAN_VERSIONto thismeridian's own version where it names an older one. A base pinned by digest (plugin-python:<version>@sha256:…) takes the new release's digest, read fromghcr.ioas anydocker pullreads it, with no sign-in. Where it cannot be read, the old digest is dropped, never kept under the new tag, and that is left by hand (base-digest); - runs each step between the two releases, in order, each over what the one before it wrote;
- writes every file that changed, all at once, after the last step;
- runs
meridian plugin checkon the result; - reports what each step rewrote, what is left by hand, each with its rule, file and line, and the check's result.
A plugin already on the target is only checked.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--to |
<version> |
the latest release | The release to move to. Never an older one than the plugin pins. |
--dir |
<dir> |
. |
The plugin's directory. |
--image |
<ref> |
ghcr.io/open-meridian/plugin-python:<the latest release> |
The SDK image the steps run in. The latest release carries every step there is. Name another to migrate with an SDK built locally, such as plugin-python:local from meridian-python's make base-image. |
--force |
off | Migrate a directory whose changes git does not hold yet, or that is not in a git repository. | |
--run-tests |
off | Run the plugin's tests in the check, as plugin check --run-tests does. |
|
--json |
off | One JSON object on stdout instead of the report. |
Where the steps come from, and where they run¶
The migrations are the SDK's, released inside it: the meridian.migrations package in open-meridian, one directory per step, each holding a record, migration.toml, and the rewrite code the record names. So every plugin-python:<version> image holds the steps up to its own release.
plugin migrate builds an image of its own from the SDK image, once, tagged meridian-migrate:<the SDK image's name>: the SDK image with the SDK's migrate extra installed, which is libcst, the library the rewrites read Python with. A plugin's own image never carries it. It runs python -m meridian.migrations --from <pinned> --to <target> in that image with no network and a read-only filesystem, mounting nothing. The plugin's Python files, its pyproject.toml and its Dockerfile, pins already moved, go in on stdin; the steps, the new text of each file that changed, and what is left by hand come back on stdout. Only meridian writes the plugin's directory, and only once every step has run.
An SDK image released before migrations existed (open-meridian 0.7.0 and earlier) carries none. plugin migrate says so, and asks for an --image that does.
The rewrites keep a file's formatting and comments wherever they change nothing. They do not reformat what they change to your formatter's taste: run your formatter after migrating.
What each step does¶
| Step | Rewrites | Leaves by hand |
|---|---|---|
| 0.5.0 to 0.6.0: access to a plugin is read or write, and a plugin declares no tags | tags in [tool.meridian], with the comment directly above it (tags-undeclared); any(a in held.read for held in caller.access) to a in caller.read, and the same for write (access-any); accounts gathered over the tags into one set to caller.read or caller.write (access-union); Caller(access=(TagAccess(...), ...)) to Caller(read=..., write=...), each the union over the tags (caller-read-write); an import of TagAccess nothing uses (tag-access-import) |
caller.access read by a tag's own name (access-tag-by-tag); TagAccess still named (tag-access); plugin.identity.tags (identity-tags); tags that were declared, whose holders a deployment admin now gives read or write (tags-granted); a tags declaration it could not remove (tags-declared) |
| 0.6.0 to 0.6.1 | Nothing: only the pins move | |
0.6.1 to 0.7.0: the unlinked refusal is meridian.NotLinked |
a meridian error's words tested for "not linked" ("is not linked" in str(err), or in err.detail) to isinstance(err, meridian.NotLinked), dropping the isinstance(err, CallFailed) and err.kind == "refused" beside it (not-linked-isinstance); except meridian.CallFailed as err: if <that>: ... else: raise to except meridian.NotLinked: ..., in a try's last handler (not-linked-except); a test's CallFailed(topic, "refused", "... is not linked ...") to NotLinked(topic, "...") (not-linked-raised) |
the words "not linked" matched anywhere else in the plugin's code (not-linked-by-text) |
| 0.7.0 to 0.7.1 | Nothing: only the pins move. 0.7.1 is the first release that carries the migrations | |
| 0.7.1 to 0.8.0: a new interface version | Nothing: only the pins move | |
| 0.8.0 to 0.9.0: a new interface version; and the asset class a plugin reports with a miss is an enum | report_missing_instrument's asset_class, a string naming one of the seven classes in another case or with its prefix ("EQUITY", "asset_class_cash"), to the class's spelling ("equity", "cash") (asset-class-spelling) |
an asset_class string naming no class, such as "etf" (asset-class-unknown); one the migration cannot read, such as a variable (asset-class-computed) |
| 0.9.0 to 0.10.0: a new interface version; pages carry their levels, and a session the level it was opened at | Interface(admin_pages=...) to pages= (admin-pages-keyword); a Page(path, title) naming no levels to Page(path, title, levels=["admin"]) (page-at-admin) |
caller.deployment_admin read to decide who is served, which opens no page since SDK 0.10.0: declare the page at admin or ask caller.admin (deployment-admin-gate); admin_pages read as an attribute (admin-pages-read); admin pages passed as Interface's third argument (admin-pages-positional) |
0.10.0 to 0.10.1: pages answer HEAD and refuse a large body; assert_no_account_data looks for account data, not identities |
Nothing: only the pins move | |
| 0.10.1 to 0.11.0: a new interface version; a plugin may report figures on its Summary, and a reported health stands until it is reported again | Nothing: only the pins move | |
0.11.0 to 0.12.0: a new interface version; a plugin hears what its roles hear with receive, and reads within its read scope; a statement names its external account and states its figures per margin segment; a holding carries its cost, lots and margin requirement as reported |
a record_holdings_statement call's flat buying_power=, margin_requirement= and maintenance_excess= to figures=[StatementFigures(segment="", ...)], the account's figures as a whole, importing StatementFigures from meridian (statement-flat-figures) |
a record_holdings_statement call naming no external_account_id, which a sidecar at v7 refuses from a plugin built for it: pass the external account the statement was read for, the one its rows name, and institution=, the institution holding it, where the connector says (statement-external-account) |
| 0.12.0 to 0.13.0: a new interface version; the book of record's operations, reads and deliveries, and what of a holding cannot move | Nothing: only the pins move | |
0.13.0 to 0.14.0: a new interface version; the book refuses an incomplete entry, naming each missing field in CommandRefused.fields, and a Caller names the delegation it came through |
Nothing: only the pins move. A plugin that sent a lot of unknown cost or the "not stated" settlement bucket completes the entry first, whatever SDK it is built on: see What the book requires | |
| 0.14.0 to 0.15.0: a new interface version; a deployment mints its own instrument records, a resolve may state what the source says of a security, and the book requires each instrument's asset class and currency | a resolve result's .placeholder to .minted (resolve-minted) |
a book position's .placeholder, which is gone: drop it, and send the person to the dashboard's Instruments page where the book refuses an incomplete record (position-placeholder) |
| 0.15.0 to 0.16.0: a new interface version; the edge keeps its own: the version's declaration, a row's raw record and provenance, values as reported, each asset counted once. See Keep what your custody plugin converts | Nothing | an external account's venue_account_type= (account-kind), a holding's also_counted_in_cash= (counted-once) and currency_assumed= (currency-provenance), each replaced by the plugin's own conversion |
| 0.16.0 to 0.17.0: a new interface version; the deployment serves its MCP: a route's one typed record of inputs, and tools derived from typed routes. See Offer your pages to agents | Nothing | a page or route that changes something and declares no params=: give it its record, or tool=False with why= (typed-route) |
| 0.17.0 to 0.18.0: a new interface version; a plugin files a ticket for a person and reads what it filed | Nothing: only the pins move | |
| 0.18.0 to 0.19.0: a new interface version; a custody plugin reports the custodian's activity, and operations reads, hears and links it, and each sync status the street keeps; a setting may be a table of typed columns. See 0.18.0 to 0.19.0 and Set a plugin's settings | Nothing: only the pins move | |
| 0.20.0 to 0.21.0: a new interface version; a plugin at the edge declares the kinds of raw record it keeps, and past each kind's window archives, keeps or deletes them as its admin chose, each move reported. See The archive and 0.20.0 to 0.21.0 | Nothing: only the pins move. A plugin keeping one retention (Storage(retention_days=...)) keeps it. One adopting kinds drops any setting of its own that held a window, and its release notes name it and the window it maps to, for the admin to set once at upgrade; a setting of its own named as a declared kind's window setting is reported by plugin check (window-settings) |
|
| 0.19.0 to 0.20.0: a new interface version; a person's access to a plugin is granted per role, and a page, route, tool and setting names the roles it serves; a custody plugin re-resolves an activity once its instrument resolves, which operations reads and hears. See Roles and 0.19.0 to 0.20.0 | Nothing: only the pins move. A plugin holding one role, or none, names no role anywhere; one that comes to hold a second names roles= on every page, route, tool and setting, which plugin check reports (roles-declared) |
|
0.21.0 to 0.22.0: a new interface version; the lake: a data plugin's catalogue, prices and bars recorded in batches, wants, the lake's reads by business date, as of and side by side, venues resolved, the data suite; a Money names its cash instrument, and every date field takes a datetime.date. See The lake |
Nothing: only the pins move. Money(amount, code) keeps its shape |
a test comparing a Money read back with one it made, which now carries instrument_id: compare amount and currency_code; a date sent as text that is no date, now refused |
0.22.0 to 0.23.0: a new interface version; the lake's trades and quotes: a data plugin records them in batches, signal and ems read and hear them, every trade in full and caught up after a watermark, the latest quote per asset; a price heard is conflated per its asset too; reporting resolves an identifier; the data suite's eight 1b cases, a case about a kind of data the plugin never publishes not presented with why, signal's 1b cases, the ems suite and reporting's resolves-its-reporting-currency. It needs chart 0.1.294 or later. See Trades and quotes |
Nothing: only the pins move | a data plugin whose suite test runs the data suite meets the eight new cases unmapped: map each, or, where it publishes no trades or quotes, name them in not_presented with why (see Write a data plugin) |
0.23.0 to 0.24.0: a new interface version; an act can be prepared rather than made (checked_by=, accounts=, made_on=, request.prepared()); a setting may be each person's own (per_person); a plugin links only to the accounts a deployment admin granted it; a data plugin reads its own datasets' licences and backfills a restart's gap; the data suite's three new cases; each role's common part in meridian.roles, typed settings and meridian.environment(); and the roles dgm, ccm and match renamed data, connectivity and confirmation. It needs chart 0.1.298 or later. See 0.23.0 to 0.24.0 |
the role dgm, ccm or match named where a plugin names a role (a roles= argument, the role meridian.suites.run, run_async, suite or names is given, or Caller.level_for, read_for or write_for asks) to data, connectivity or confirmation (role-renamed); and the same in pyproject.toml's [tool.meridian] roles (metadata-role-renamed) |
a form field named prepare, which the SDK refuses; each act a verified plugin offers naming its check tool, which role-pages requires from CLI 0.1.39; a data plugin's three new suite cases, each mapped or, where its source never presents it, named in not_presented with why (see Write a data plugin) |
0.24.0 to 0.25.0: a new interface version; a service account runs a plugin's autonomous work: an act called as the role's service account (meridian.runs_as(role)), answered made or prepared, a refusal naming its field (NotGranted.fields, CallFailed.fields); a caller who is a service account (Caller.subject_kind, Caller.owner_subject, meridian.SubjectKind); and a tool called as one in a plugin's own test (PageClient.call_tool(subject_kind=, owner_subject=)). It needs chart 0.1.299 or later. See 0.24.0 to 0.25.0 |
Nothing: only the pins move | |
0.25.0 to 0.26.0: a new interface version; the order path's slice A and settlement calendars: the order path's typed operations and deliveries, its refusal codes and the transition table (meridian/transitions.json); read_calendar and compute_date; a version's algorithms (Declaration(algorithms=), AlgorithmDeclaration, runs_as(role, algorithm_id=), Caller.algorithm_id); the connectivity plugins a caller may route through (Caller.connectivity_instance_ids); a sleeve on positions, lots and movement lines; the suites' v22 cases. It needs chart 0.1.301 or later. See 0.25.0 to 0.26.0 |
Nothing: only the pins move | a suite test meeting the settlement calendars' cases (reads-a-calendar, every role's; asks-for-a-settlement-date, every role's but connectivity's and operations'): map each, or, where the plugin's work never uses calendars or settlement dates, name it in not_presented with why (see A case not presented) |
0.26.0 to 0.27.0: a new interface version; venue-originated fills and the lake's outcomes: a fill on no placement (report_execution's instrument_id, side, venue_order_id, venue_client_order_id), waiting unmatched (Execution.state); the ems's accept and DK (accept_execution, dont_know_execution) and their refusal codes; a connectivity plugin hearing the executions it reported; VenueCatalogue.execution_acknowledgement; list_orders(execution_state=); a value-date roll (report_value_date_roll), financing and commission fees, an order's quote_currency; an event contract's outcomes (record_outcomes, receive(outcomes_recorded=), list_outcomes); the closed-day flag and counts; meridian.account_currency(); the suites' v23 cases, their about markers and any_of. See 0.26.0 to 0.27.0 |
Nothing: only the pins move | |
0.27.0 to 0.28.0: a new interface version; booking (book_order, hand_off_booking, correct_booking, set_allocation_instruction, record_sleeve_cross, report_give_up, raise_alert, acknowledge_alert, list_bookings, list_alerts); every instrument named by the platform's INS- ID, a local LCL- record provisional (ResolveIdentifierResult.provisional); list_replacements and the current-ID map, list_templates, read_template, offer_template_fields; read_local_record_orders; the suites' v24 cases; DatasetDeclaration.calendar_id; meridian.environment() defaulting to sandbox. It needs chart 0.1.305 or later. See 0.27.0 to 0.28.0 |
a record's instrument_id.startswith("LCL-") to its provisional (lcl-prefix-provisional); the platform's ID read from the open_meridian identifier to the record's instrument_id, or the read's default where the record is provisional (open-meridian-instrument-id); a test's LCL-suite-n to INS-suite-n (suite-fixture-ins) |
an ID tested for the prefix LCL- with no record at hand (lcl-prefix-tested); the open_meridian identifier read or written another way (open-meridian-read); a plugin keeping instrument IDs in its own storage, which follows each replacement with meridian.CurrentIdMap; a plugin that was Live by default, which says environment(default="live") only where Live is meant; a suite test meeting v24's cases |
0.28.0 to 0.29.0: a new interface version; plugins shared through registries and carrying their own documentation: the whole declaration (Declaration(interface=, reads_external_accounts=, documentation=, affirmation_deadline=)), meridian-declaration --documentation, registration held to the version uploaded, connect refusing an interface its declaration does not name; record_break(unresolved_identifiers=), resolve_venue(venue_id=); the give-up cases about give-ups. Breaking: a plugin whose declaration does not carry its interface is refused at registration until it does. It needs chart 0.1.306 or later. See 0.28.0 to 0.29.0 |
a connectivity plugin's await plugin.read_local_record_orders() removed (escape-hatch-read-dropped), and its test's mapping of reads-whether-orders-may-name-local-records (escape-hatch-case-dropped) |
connect given an interface, settings or reads_external_accounts: declare them in the Declaration, the port fixed, and pass connect(declaration=DECLARATION) (connect-names-the-declaration); a [tool.meridian] naming no declaration: name one, and write docs/overview.md and docs/setup.md (declaration-not-named); a Dockerfile whose last stage copies no documentation into the image, such as one installing only the package: COPY docs/ docs/, or the whole context as the templates do (docs-not-in-image); a connectivity plugin's read of the escape hatch whose answer it uses (escape-hatch-read-kept) |
0.29.0 to 0.30.0: each role's common logic in the SDK: meridian.roles.data (Wants, Venue, the venue's reading, served, want_rows, VenueConnection), meridian.roles.data.pairs and .event_contracts, meridian.roles.data.testing, meridian.edge.RawStore, and meridian.roles.connectivity (Trader, its Trading page, testing.Rig); a connectivity role template. It needs chart 0.1.307 or later. See 0.29.0 to 0.30.0 |
a module copied from a sibling plugin (market.py, products.py, contracts.py, raw.py, dgm.py) into a re-export of the SDK's names (copied-module-onto-sdk); a trading layer in SnapTrade 0.16.0's shape into a re-export of meridian.roles.connectivity (copied-trader-onto-sdk) |
import from the SDK and delete the re-exports, constructing Trader with market_zone= and Sent for Noted (re-export-left, trader-re-export-left); the copied pages onto the kind's builders and templates and trading_read (copied-page, trading-page-onto-sdk); the copied tests onto one class of the SDK's checks and testing.Rig (copied-tests, trading-rig-onto-sdk); a copied stream.py kept as the plugin's own transport (stream-is-the-plugins), retarget for an assigned connect (stream-connect-assigned); the Makefile's suite-same removed (suite-same-retired); a trading layer not rewritten, moved by hand (trader-by-hand) |
0.30.0 to 0.31.0: a new interface version; the analytics component, the pricing role, event groups and the agents' guard: meridian.analytics (a job's typed builders, Answer, Jobs) and its reference computations; meridian.roles.pricing.Pricer and a pricing role template; meridian.EventGroups and event_contracts.Feed.report_event; environment(demo="paper"), reached_environment, connection_badge and Caller.agent_refused; plugin.query_lake and plugin.count_model_tokens; and custody's pages (custody.account_pages), a broker's listings (roles.data.listings) and the Trader's charges and stated settlement dates. Breaking: every role's suite holds new cases. It needs chart 0.1.308 or later. See 0.30.0 to 0.31.0 |
a base pinned by digest at an older release moved to 0.31.0, its digest dropped where it cannot be read (base-digest-dropped) |
pin the base's digest again (base-digest); map each role suite's v26 cases, or name one the source never presents with why (suite-cases-v26); give a connectivity adapter its environment, read with meridian.reached_environment (venue-reports-no-environment); move custody pages, a listings layer or a trader of the plugin's own onto the SDK (custody-pages-onto-sdk, lake-onto-sdk, trading-onto-sdk) |
0.31.1 to 0.32.0: a new interface version; the analytics component's pnl version 2 and execution_cost, a job's business date and reporting currency; the lake's rates (meridian.roles.data.rates); latency recorded and not yet reported (roles.Latency), a dataset's liveness and declared delay; the session ceilings', dataset thresholds' and stale-reference settings; the ems's reference check and an RFQ placement's quotes; the Trader's placement types and RFQs; connectivity's pages declared by the SDK; Feed.keep_events. Breaking: every role's suite holds new cases. It needs chart 0.1.309 or later. See 0.31.1 to 0.32.0 |
a base pinned by digest at an older release moved to 0.32.0, its digest dropped where it cannot be read (base-digest-dropped) |
pin the base's digest again (base-digest); map each role suite's v27 cases, or name one the source never presents with why, and the three about latency until chart 0.1.313 (suite-cases-v27); a data plugin recording no trades names queries-the-lake-within-bounds not presented (lake-query-without-trades); move a connectivity plugin's own Trading page onto the SDK's (trading-page-onto-sdk) and an event-contract plugin's own events loop onto keep_events (events-onto-sdk); upload the e2e logs with hidden files (e2e-hidden-files) |
0.32.0 to 0.32.1: the data suite's an-exact-price and a-want-recorded-against are about closes, and meridian.roles.data.testing.RATES_NOT_PRESENTED names every case a rates-only plugin never presents. See 0.32.0 to 0.32.1 |
Nothing: only the pins move | a data plugin recording only rates names the two cases not presented |
0.32.1 to 0.32.2: DatasetLicence writes redistribution, attribution_required, attribution_text, copyright_class and no_more_restrictive_terms; a data plugin's Connection draws Sessions only once a session is measured; the testing Recorder answers RecordRates; RATES_NOT_PRESENTED no longer names records-a-datasets-liveness. See 0.32.1 to 0.32.2 |
Nothing: only the pins move | a rates-only plugin recording no liveness names records-a-datasets-liveness not presented, with why |
0.32.2 to 0.32.3: rates.Feed.rate refuses a rate with no convention; a missed rates subject is asked again the same day; a listings feed records its live dataset's liveness; Resolution.challenge_ends_at_ns reaches the Outcome; DatasetLicence writes not_affiliated_notice; the merged Connection draws one group at a time under a tab row. See 0.32.2 to 0.32.3 |
Nothing: only the pins move | a rate built with no convention: give it one |
0.32.3 to 0.33.0: a new interface version; the typed operations take meridian.SubjectRef only, which carries its role; Latency.report sends ReportLatency, and the three latency cases are required; an ems reads the stale-reference actions the deployment permits; listed options' and futures' chains, lifecycle events and package orders (roles.chains, roles.lifecycle, roles.packages); a signal's derived series, saved scenarios and quality escalated (roles.signal); the filings slice (roles.data.filings); quality states at the edge (roles.data.quality); DataEligibility; every role's v28 cases, each marked by its about. Breaking. It needs chart 0.1.313 or later. See 0.32.3 to 0.33.0 |
a wire SubjectRef handed to the SDK (ops.SubjectRef(...), operations_pb2.SubjectRef(...)) into meridian.SubjectRef(...) (subject-ref-from-the-sdk); a base pinned by digest at an older release moved to 0.33.0, its digest dropped where it cannot be read (base-digest-dropped) |
a heard message's subjects passed to a typed operation: convert each (subjects-from-the-wire); map each role suite's v28 cases, or name one about what the plugin never presents with why (suite-cases-v28); map the three latency cases a v27 plugin named not presented (latency-cases-required); pin the base's digest again (base-digest) |
0.33.0 to 0.33.1: additive: an event-contract Market may answer bars(contract, interval, first_ns, until_ns) (Candle, HOUR, DAY), its plugin declaring a third dataset keyed bars (meridian.v1.Bar, on its session day) and running Feed.keep_bars(declared); EventContractsChecks accept bars where the hook is supplied and refuse any other third dataset; the feed-delay producer stands on the quote where the last trade states no time or no prices are declared; a row stating an interval is no duplicate of the same values over another |
only the pins move: no migrate step is needed beyond them | a venue keeping bars declares bars, names BARS_DECLARED in its venue_cases and drops a-daily-bar from NOT_PRESENTED; a plugin's own feed-delay producer standing on the quote can be removed |
| 0.33.1 to 0.33.2 | Nothing: only the pins move | |
| 0.33.2 to 0.33.3 | Nothing: only the pins move | |
| 0.33.3 to 0.33.4 | Nothing: only the pins move | |
| 0.33.4 to 0.33.5 | Nothing: only the pins move | |
0.33.5 to 0.34.0: fixed income: the money rules, accrual and factors (helpers.debt), the seven fixed-income records reported and servicing's expectations (helpers.lifecycle), analytics.DEBT_MEASURES and their reference computations (analytics.debt), a quality assessment's data type, a derived value in money, the suites' factor cases and the widened about markers; meridian.roles holds only roles, its topic modules chains, packages, lifecycle and debt moved to meridian.helpers with no shim. Breaking |
every import and name of meridian.roles.chains, .packages, .lifecycle or .debt onto meridian.helpers (import meridian.roles.chains, from meridian.roles import chains, from meridian.roles.chains import X; from meridian.roles import Latency, chains split in two; meridian.roles.debt.factor(...), a patch target's string); a base pinned by digest at an older release moved to 0.34.0, its digest dropped (offline) |
map each role suite's v29 cases, or name those about what the plugin never presents; name a QualityAssessment's data_type; an old path still named in a comment, a dynamic import or read through from meridian.roles import *; pin the base's digest again |
0.34.0 to 0.35.0: each role is one class: meridian.roles.<role>.Role for every role, generated from the matrix and the suites, its acts methods and its events on_<row> methods, required ones abstract; seven role modules added (compliance, confirmation, oms, portfolio, reference, servicing, settlement); meridian.run and run_async the entry point, on_start, run (restarted with helpers.sessions.Backoff) and on_stop, seed, subjects() and hear_again() per class on one stream; connect(roles=) required; the connection keeps only what is plugin-wide, and makes no act; receive gone; connectivity.Trader, pricing.Pricer and the data feeds role classes; helpers given the role object; Recorder.bind and Recorder.hear; meridian.behaviours; 924's helpers -- helpers.sessions.Supervisor, helpers.calendars (DayClock, MarketDays), data.SentRows, Conflator, Timetable, timeliness, api_key, the listings feed's timeliness, two clocks, MIC venues, edge and pages, data.option_chains, pairs.Feed(conflate_first=, day_clock=) and pairs.supervised -- each held to behaviour cases. Breaking |
a receive call whose handlers are one class's methods on one declared role's events made that class's on_<row> methods, the class given the role's base, seed=False its seed = False, the connection it took dropped; the SDK's own handlers dropped from a receive; a Trader's, a feed's and a Pricer's renamed methods and arguments, the connection given them dropped (a recorder's call recorder.bind(...)); self.plugin.<act> on self in a role class, a helper given self.plugin given self; an unchanged template entry point replaced; a listings feed's own at_the_edge and the _record override calling it deleted (the feed does both), and the _changed copies made data.SentRows, a name only they imported dropped |
an entry point connecting by hand, moved to meridian.run; an act on the connection, made on the role object holding it; a receive no rewrite reached; a role class lacking a required event; a suite test hearing through recorder.receive or giving a class a stand-in connection; 924's helpers adopted by hand (helper-adopted): a stream supervisor of its own onto helpers.sessions.Supervisor over its Link, a chains machine onto data.option_chains.Feed, a calendar reader and a day by hand onto helpers.calendars, listings pages onto the SDK's, a polling loop onto data.Timetable, a plan switch onto the listings feed's timeliness, the again and licences loops onto a role's run(), an FX lake onto pairs.Feed(conflate_first=True), and a raw store onto meridian.edge.RawStore |
0.35.0 to 0.35.1: a fix: the pairs and event-contract feeds resolve their venue through the feed, not the connection; the suites' Recorder.plugin a stand-in for the connection that makes no act, a PageClient given a recorder handing its pages the same; meridian.run applies the first settings before on_start and keeps the settings watch; a reopened stream reads the latest only of rows seeded; an event-contract feed asks for its venue once a contract is served; the 0.34.0 and 0.35.0 steps rewrite more (a feed's own handlers dropped from receive, a recorder's receive outside the tests heard, a later base pin kept) and name less by hand |
only the pins move: no migrate step is needed beyond them | a plugin's test acting through recorder.plugin or a page's request.plugin, or asserting a class's plugin is the recorder, now fails as the plugin would when run: the act made on the role object, the assertion on recorder.plugin |
0.35.1 to 0.35.2: a fix: a typed delivery of the settings (meridian.run's on_settings, plugin.settings(Config), first_settings) holds every setting delivered in config.values, the storage kinds' <kind>_window_days and <kind>_past_window the SDK declares among them, which it dropped; RawStore.tend(plugin, settings=config, today=...) reads the kind's window from the settings as delivered, typed or not, today the UTC date where not given; no field may be named missing, refused or values |
only the pins move | a Declaration(settings=Config.declared()), the 0.35.1 way to reach the window settings (typed-settings-declared): declare settings=Config, so on_settings receives a Config (a handler reading delivered.values reads them unchanged; one reading .missing_required reads .missing), and give it to RawStore.tend(..., settings=config) in place of the windows copied by hand |
0.35.2 to 0.35.3: a fix: helpers.sessions.Supervisor hands heard every message received ([] for a heartbeat, waits at least a failure's retry_after, and opens a new session for what is wanted with reopen_on_want=True (pairs.supervised does); SessionState.label; a row delivered latest value first keyed by its own kind (a rate or a statistic no longer fails the hearing loop) and by its series key; a table setting's rows a list, typed or not; RawStore.tend reads from the settings only what it is not given, keeps what is past a window given alone, refuses a window below 0 or an unknown past; a typed delivery's SDK settings read only; Recorder.hear(role, *rows); PageClient refuses a plain stand-in's acts; Declaration(pricing_models=[PricingModelDeclaration(...)]); the 0.33.0, 0.35.0 and 0.35.2 steps rewrite and name more |
only the pins move | a named override calling dataset (named-calls-dataset); a method timeliness(...) hiding a listings feed's property (timeliness-overridden); a role class's field subjects (subjects-shadowed); a 0.34 handler name reached through another name (old-handler-name); a receive(...) left (receive-gone); a test reading <x>.plugin.on(...) (stand-in-read); a role class made outside the entry point, a test or a bind(...) (unregistered-role-object); a base pinned by tag alone where the plugin pins by digest (base-digest); and, by behaviour, a heard given to a Supervisor now handed [], and a table's rows read as a tuple, now a list |
0.35.3 to 0.35.4: a fix and an addition: a book record's view keyed by its entity (views.ENTITY_KEYS), an order whose venue's ID is set later held once; a quote stating no source time timed by its valid_from_ns (roles.source_ns, confirmed_ns), so ems.ReferenceCheck no longer judges it stale; the views' stream surviving a read again that fails outside gRPC; pages.live_values and LiveValue.of, a page's values worked out from its views with source, age and staleness counted only in session, served by pages.live to the kit's <om-live cells>; every template's AGENTS.md says so |
only the pins move | a live module of the plugin's own, its cells worked out from views (live.py's Board), moved onto pages.live_values and LiveValue.of, and its page script onto <om-live cells>; by behaviour, a book view's row keyed order_id=<id> alone (a key built from every identifier no longer matches), and a quote with no source time no longer stale by the pre-trade check |
0.35.4 to 0.35.5: additive: helpers.sessions.FX_WEEK, the FX week's trading sessions, Sunday 17:00 to Friday 17:00 New York, the 17:00 roll in New York's own time on both sides of daylight saving, usable wherever sessions are taken (LiveValue.of, ems.ReferenceCheck.stale, roles.in_session); the base template links kit 0.16.0 (pages.KIT) |
only the pins move | an FX week of the plugin's own (a TradingSessions for Sunday to Friday 17:00 New York, the samples' FX_WEEK), replaced by helpers.sessions.FX_WEEK; by behaviour, a page on the base template loads kit 0.16.0 where the deployment carries it |
0.35.5 to 0.36.0: standing analytics (analytics.job(..., standing=True), analytics.measure(..., refresh=, cadence_ns=, material_move_bp=), analytics.Jobs().standing(self, job) answering a LiveView, plugin.subscribe(..., job_ids=), meridian.MeasureRef); a stream that names the lake's rows or carries a view, silent past receive.QUIET_SECONDS, opened again and caught up; an act or read refused for load asked again after its retry-after (client.load_wait); a dataset's terms_and_pricing in place of its licence (licence_default, DatasetLicence and DataEligibility gone), sessions_calendar_id, gap_recovery; Declaration(most_shards=); an event contract's terms and Contract.category stated at its resolve; the source priorities read; the suites' holds-a-standing-figure and reads-the-source-priorities. Breaking. It needs chart 0.1.538. See 0.35.5 to 0.36.0 |
each literal licence_default=DatasetLicence(...) into terms_and_pricing="...", the terms it declared in words, the retired names dropped from the import (licence-default-described); a base pinned by digest at an older release moved to 0.36.0, its digest dropped (base-digest-dropped) |
a licence the rule could not read, or DatasetLicence, DataEligibility or .licence_default still named (licence-default-left); an SDK DerivedSeries or CorporateActionAnnounced given the wire's MeasureRef (measure-ref-sdk-form); map each role suite's new cases, or name one not presented with why (suite-cases-v30); pin the plugin's e2e images to a core of this release (core-images); pin the base's digest again (base-digest) |
0.31.0 to 0.31.1: a saved setting value no longer among its choices, or not of its kind, is delivered unset rather than raised (Settings.refused, meridian.RefusedSetting, request.own_settings_refused); an event-contract venue's map keeps a range market's bounds, and event_contracts.Feed.stated_identifiers; the pricing template declares no storage; the templates' AGENTS.md on publishing to a registry. See 0.31.0 to 0.31.1 |
Nothing: only the pins move |
plugin migrate adds three rules of its own: base-digest, for a base pinned by digest whose new digest could not be read, with the docker buildx imagetools inspect that names it; pin-elsewhere, for the old release still named in another file (a Makefile's base image, a workflow, a README), which it reports rather than moves because some of those are history; and unreadable, for a file that is not UTF-8, or that a step could not read as Python, which it leaves as it was.
Output¶
In text, the pins it moved, each step with the files it rewrote and how many places each rule did, what is left by hand with what to write instead, and then the check's own report:
meridian plugin migrate: ., open-meridian 0.5.0 to 0.7.0, the steps run in ghcr.io/open-meridian/plugin-python:0.12.0
pins pyproject.toml:10 open-meridian==0.5.0 -> open-meridian==0.7.0
Dockerfile:2 ghcr.io/open-meridian/plugin-python:0.5.0 -> ghcr.io/open-meridian/plugin-python:0.7.0
0.5.0 to 0.6.0 Access to a plugin is read or write, the same for every plugin, and a plugin declares no tags (decisions/026).
pyproject.toml tags-undeclared
src/desk/access.py access-any (3), access-union (2)
0.6.0 to 0.6.1 A new account created by a link carries its custodian, type, owner and note: …
nothing to rewrite: only the pins move
0.6.1 to 0.7.0 A row refused because its external account is not linked raises meridian.NotLinked, …
src/desk/record.py not-linked-except, not-linked-isinstance
Wrote Dockerfile, pyproject.toml, src/desk/access.py, src/desk/record.py.
Left by hand, 1 place(s): do each as it says.
access-tag-by-tag src/desk/access.py:33: return any(held.tag == "statements" and held.write for held in caller.access)
instead: read caller.read and caller.write, … A tag's name is gone: decide whether what it guarded is showing (read) or doing (write)
meridian plugin check: ., by the rules of meridian 0.1.18
…
With --json, one object. image is null when the plugin was on its target already; line is null for a file as a whole; a place left by hand has the step it came from in from and to, or null for plugin migrate's own rules; check is plugin check --json's object; done is true when nothing is left by hand and every rule holds:
{
"dir": ".", "meridian": "0.1.18", "sdk": "open-meridian",
"from": "0.5.0", "to": "0.7.0", "image": "ghcr.io/open-meridian/plugin-python:0.12.0",
"done": false,
"pins": [{"file": "pyproject.toml", "line": 10, "from": "open-meridian==0.5.0", "to": "open-meridian==0.7.0"}, …],
"steps": [{"from": "0.5.0", "to": "0.6.0", "summary": "…", "breaking": true,
"rewrote": [{"file": "src/desk/access.py", "rule": "access-any", "what": "…", "places": 3}, …]}, …],
"wrote": ["Dockerfile", "pyproject.toml", "src/desk/access.py", "src/desk/record.py"],
"by_hand": [{"rule": "access-tag-by-tag", "file": "src/desk/access.py", "line": 33,
"found": "…", "instead": "…", "from": "0.5.0", "to": "0.6.0"}],
"check": {"passed": true, …}
}
breaking says whether a plugin left on the old release's code fails on the new one (0.5.0 to 0.6.0, 0.8.0 to 0.9.0, 0.11.0 to 0.12.0, and 0.14.0 to 0.15.0), rather than keeping a form the new release no longer promises (0.6.1 to 0.7.0, whose refusal still says "is not linked" today, and 0.9.0 to 0.10.0, which still takes admin_pages as pages at admin, with a DeprecationWarning). It is about the code once the pins move: a plugin left on 0.11.0, pins and all, keeps working on a newer sidecar, which reads its flat figures as the set with no segment and its statement's account from its rows.
Exit codes: 0 migrated, nothing left by hand, and every rule holds; 1 something is left by hand or a rule does not hold, or it could not run (docker failed, or the SDK image carries no migrations), in which case nothing was changed; 2 asked wrongly, or refused before changing anything: --dir not a directory, no pins or pins that disagree, a --to older than the pin or not released, no recorded steps from the pinned release, or changes git does not hold yet without --force.
Plugin commands on a deployment¶
plugin upload, list, launch, stop, dev, logs, events and open act on the delegation meridian connect made. The deployment answers upload, list, launch, stop, dev, logs and events for a deployment admin only. It answers open for anybody who holds a level on that plugin, at a level they hold. See Access.
These flags are shared among them:
| Flag | Argument | Default | Used by | Meaning |
|---|---|---|---|---|
--deployment |
<addr> |
the one deployment connected | all | Which connected deployment, when you are connected to more than one. |
--dir |
<dir> |
. |
upload, dev |
The plugin's directory. Its image is built with docker, from its own Dockerfile. |
--instance |
<id> |
none (required) | launch, dev, logs, events, open |
The instance's name. Its page is found by it. Lowercase letters, digits and single hyphens, starting with a letter, at most 63 characters. |
--yes |
off | launch, dev |
Approve the roles the version asks for without being asked. For a script that has already shown them to a person. | |
--json |
off | dev, logs, events, open |
JSON on stdout: one object, or one per line for dev and --follow. Progress goes to stderr. |
|
--release |
off | dev |
Upload the plugin as it is now as a version, and run that version in place of the live instance. | |
--since |
<revision> |
none | logs, events |
Only what came after that revision, a whole number. |
--follow |
off | events |
Keep reporting events as they happen, until interrupted. | |
--print |
<path> |
none | open |
The page at that path on the plugin's host, as you are served it, instead of a link. |
--level |
manage, open or view |
the first level you hold | open |
The level the session is opened at, as the dashboard's buttons: manage (admin), open (write) or view (read). |
Exit codes for all of them: 0 done, 1 refused or failed, 2 asked wrongly, 3 not connected, or the delegation was revoked or has lapsed.
meridian plugin upload¶
Builds the plugin's image on this machine and puts it in the deployment's catalogue as a version. The steps:
- Read
pyproject.tomland check its plugin manifest. A[tool.meridian]that declarestagsis refused here, before anything is built: a plugin declares none, since a person's level on a plugin isadmin,readorwrite, in the deployment's access groups. - Build the image with
docker build, taggedmeridian-plugin/<name>:<version>. - Run the SDK's
meridian-declarationin the built image, with no network, for the version's declaration, as[tool.meridian]'sdeclarationnames it; and, where the declaration names documentation,meridian-declaration --documentationfor the texts, each held to the digest and size the declaration names before anything is sent. - Read the image back with
docker save. - Push it into the deployment's registry through the dashboard. A layer the registry already holds is not sent again, and one another plugin's repository holds is mounted from there.
- Send the manifest's metadata, the declaration and the texts with the image's digest. The deployment records the version, and refuses texts that do not match the declaration: a text whose digest or size is not the declaration's, a section outside the five, or one past its bounds, and its screen refuses an upload whose documentation or declaration matches a refusing rule: override, acting for the reader, a claim of endorsement, verification or regulatory readiness, posing as Open Meridian, exfiltration, concealment, or claiming authority (see Write a plugin's documentation). The texts are kept with the version and never replaced.
A version is recorded once and never replaced: uploading a name and version already in the catalogue is refused. On success it prints Uploaded to <address>, as sha256:<digest>., and, for a version carrying documentation, where a deployment admin reads it before launching it: the version's Documentation page, <address>/admin/plugins/versions/<name>/<version>/documentation.
meridian plugin list¶
Prints the catalogue: every version uploaded, with its roles, whether it serves a page, and the SDK version it pins, and, where it carries documentation, its Documentation page, <address>/admin/plugins/versions/<name>/<version>/documentation; and every launch, with its instance, version and state (launched, stopped or failed, with the failure), and under it, where its launch asked any, its limits, shards and standby pod. Because it needs a live delegation, it is also the way to check you are connected: it exits 3 when you aren't.
meridian plugin launch¶
meridian plugin launch <name> <version> --instance <id> [--in-flight <n>] [--requests-per-second <n>] [--answer-mib <n>] [--shards <n>] [--standby] [--yes] [--deployment <addr>]
Runs a recorded version as an instance. <name> is a local plugin's name, or, for a version installed from a registry, its listing, <namespace>/<plugin>, as meridian plugin list names it. It first prints the roles the version declares, and any limits, shards and standby asked, then asks Launch it as <id>, with these?. Anything but y or yes is no. With no terminal to ask at, it is refused unless you pass --yes.
The deployment runs the instance with exactly the roles it declares; an approval that names anything else is refused. On success it prints the instance's page address, <address>/plugins/<id>, and, where the launch asked any, reads back what the deployment kept of its limits, shards and standby: Kept on its launch, as asked: .... An older core keeps none of them, and that is said. See Its limits, shards and standby pod.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--instance |
<id> |
none (required) | The instance's name: lowercase letters, digits and single hyphens. |
--in-flight |
<n> |
the deployment's | The most requests it has in flight at its sidecar at once (limits.in_flight), over the chart's sidecar.limits.inFlight. Added in CLI 0.1.60. |
--requests-per-second |
<n> |
the deployment's | The most requests a second, with a second's burst (limits.requests_per_second). Added in CLI 0.1.60. |
--answer-mib |
<n> |
the deployment's | MiB of answers held or paged in flight for it (limits.answer_mib). Added in CLI 0.1.60. |
--shards |
<n> |
one pod | The pods of the instance sharing its subjects (shards), at most the version's most_shards, 0 to 64. Kept on the launch; the plugin runs one pod today. Added in CLI 0.1.60. |
--standby |
off | A standby pod that takes over within 10 seconds (standby). Kept on the launch; the plugin runs one pod today. Added in CLI 0.1.60. |
|
--yes |
off | Launch without being asked. For a script that has already read what it asks for. |
A limit that is not a whole number above 0 is refused before anything is sent, naming its flag and the field as core names it; core's own refusals, such as shards past the version's most_shards, come through as it words them.
meridian plugin stop¶
Stops a launched instance. The instance is named as a word here, not with --instance.
meridian plugin dev¶
meridian plugin dev --instance <id> [--dir <dir>] [--yes] [--json] [--deployment <addr>]
meridian plugin dev --release --instance <id> [--dir <dir>] [--yes] [--json] [--deployment <addr>]
Runs the plugin live on a development deployment: each save is sent as it is made, and the plugin's process restarts on it in the same pod, with the same sidecar and the same grants. A deployment not installed with --development refuses it. See Development deployments.
Without --release, it does the following:
- It reads
pyproject.tomlin--dirfor the plugin's name and version. - If the instance is already running live, it sends the directory to it and asks nothing. If the instance is running a version rather than live, it refuses: stop it first or choose another
--instance. - Otherwise it uploads the plugin, unless that name and version are already in the catalogue. In that case the uploaded version runs, with the directory's files sent over it. It then launches the instance live, after the same approval as
plugin launch. - It watches the directory until you press Ctrl-C, sending each change and reporting every event with its revision. The instance keeps running after Ctrl-C.
The directory is scanned four times a second, and events are polled twice a second. Some files are never sent:
.git,__pycache__,.venv,venv,.mypy_cache,.pytest_cache,.ruff_cache,build,dist,.meridian,.DS_Store,*.egg-infoand*.pyc;.e2e, where a plugin's e2e copies core's plugin harness out of its image, whichplugin checkdoes not read either;- whatever the plugin's
.dockerignorenames. Negations and patterns with wildcards other than a leading*are not read, and a pattern that can't be read is sent rather than guessed at.
One change may carry at most 11 MiB before encoding.
A change to the plugin's dependencies or roles needs a new version. The live code runs on the image the instance was launched from.
With --release, it does the following:
- It uploads the directory as it is, as the version in
pyproject.toml. A version already recorded is refused: raiseversionfirst. - It asks for approval of the roles.
- It stops the instance if one is running.
- It launches the new version in its place, not live.
The result is an ordinary version in the catalogue.
Output. In text, one line per event, with its revision first:
r1 sent (9 files, 0 deleted)
r1 synced (9 sent, 0 deleted)
r1 restarted
r1 ready
r2 sent (1 files, 0 deleted)
r2 crashed, exit 1
Traceback (most recent call last):
...
With --json, one JSON object per line on stdout. Each event is described in plugin dev events. With --release --json, one object:
meridian plugin logs¶
Prints what a live plugin printed, one line each. With --since, only lines from revisions after that one. Without it, everything kept, which is up to about 1 MiB of output.
With --json, one object:
revision is the revision the instance is on now.
meridian plugin events¶
meridian plugin events --instance <id> [--since <revision>] [--follow] [--json] [--deployment <addr>]
Prints what happened to a live plugin: synced, restarted, ready, crashed, exited, refused, each with its revision. With --since, only events from revisions after that one. With --follow, it keeps polling and printing new events until interrupted.
With --json and no --follow, one object: {"revision": <now>, "events": [ … ]}. With --json --follow, one event object per line. The events are described in plugin dev events.
meridian plugin open¶
meridian plugin open --instance <id> [--level manage|open|view] [--print <path>] [--json] [--deployment <addr>]
A session on a plugin carries one level, as the dashboard home's buttons do: Manage at admin, Open at write, View at read. See Manage, Open and View.
--level names it: manage, open or view, or the level's own name, admin, write or read, in any case. Anything else is refused before anything is asked, and so is a level you do not hold, by the deployment, naming the levels you do. Without --level, the session is opened at the first level you hold, Manage before Open before View, as the home's first button is.
A page serves only the levels it is declared with. So the first level you hold may well be refused the page you ask for: as a plugin's admin, which a deployment admin is on every plugin through All plugins (admin), the scaffold's Accounts page at / is refused at Manage. Name the level the page is for:
meridian plugin open --instance my-plugin --level manage --print /setup # the Setup page
meridian plugin open --instance my-plugin --level open --print / # the Accounts page, if you hold write
Without --print, it prints a link to the plugin's page, at that level. The first browser that opens the link, within a minute, is signed in to that plugin's page alone, for as long as your delegation to the CLI lasts and 12 hours at most, and lands on its /; any other path on the same host is then open to it at that level. Run open again for another browser. With --json, the level the dashboard opened it at comes too:
With --print <path>, it prints the page at that path on the plugin's host, as you would be served it in a session at that level, instead of a link:
<path>starts with a single/, and may not be under/.meridian.- A page is at most 8 MiB, and redirects are not followed.
- It exits 1 when the plugin answers with a status outside 200–299, after printing what the plugin answered. The message on stderr names the level it was asked at. A 403 at the first level you hold, with no
--levelgiven, says so, and which--levelasks at another:
meridian plugin open: my-plugin answered 403 for / at Manage (admin), the first level you hold; a page serves only the levels it is declared with, and `--level open` or `--level view` asks at another
A page built on meridian.Pages answers such a request with why, such as / is not served under Manage; it is for Open and View.
With --json, one object:
{"instance_id": "my-plugin", "status": 200, "level": "write", "content_type": "text/html; charset=utf-8", "body": "…"}
A body that is not UTF-8 text comes as body_base64 instead of body.
The registries¶
plugin release, plugin publish, plugin key and plugin download act through a sign-in to the platform, --platform's (default https://open-meridian.com), made in your browser the first time a command needs it and kept 30 days, never a password. Its access tokens live ten minutes and are good at the platform's publishing API alone; a publish uses one narrowed to its plugin, held while the build is followed and narrowed again only within a minute of its end. Where the platform will not narrow the sign-in's token, the browser opens for one narrowed sign-in, once a publish at most. meridian uninstall forgets the sign-ins. What the registries are is in Registries.
The usual path is two commands: once, meridian plugin key setup; each release, meridian plugin release X.Y.Z. Signing a tag by hand and meridian plugin publish remain the other way. See Publish a plugin to a registry.
These flags are shared among them:
| Flag | Argument | Default | Used by | Meaning |
|---|---|---|---|---|
--organisation |
<namespace> |
the organisation the plugin's git remote names | release, publish, key |
The organisation the listing or the key is its, named by its namespace. A listing is <namespace>/<plugin>. Where the default is not known, the command says to name it. |
--dir |
<dir> |
. |
release, publish, key setup |
The plugin's directory. |
--timeout |
<d> |
30m |
release, publish |
How long to follow the build. The build carries on after. |
--details |
off | release, publish |
Print each check's full text. Without it, each check is one line cut to the terminal's width. |
meridian plugin key setup¶
Makes the key release tags are signed with ready, in one step. Run it once on each computer you release from.
- The key. Where git already signs with an SSH key (
gpg.format sshanduser.signingkey), that key is the one used. Otherwisessh-keygenmakes one:ed25519, a key on this computer, in~/.ssh/id_ed25519_meridian; or with--hardware,ed25519-sk, a key held on a FIDO security key, in~/.ssh/id_ed25519_sk_meridian.ssh-keygenasks for the passphrase itself, and for a touch of the security key; the CLI never sees either, and never reads the private half. - git. It sets git to sign with it:
gpg.format sshanduser.signingkey, for your whole git. Where git already signs with a GPG key, it sets them in the plugin's repository alone, so nothing else you sign changes. - The platform. It registers the public half on the organisation, where it is not registered already. Only an Owner or Admin may. Anybody else is told to ask one, with the line to give them and the command or page they register it with.
Where the organisation is not known, as before the plugin's repository is on GitHub, it makes the key ready and says to run it again there, or to register the public half with meridian plugin key register.
meridian plugin new offers it where git has no SSH signing key yet.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--hardware |
off | Where git has no SSH signing key, make one on a FIDO security key (ssh-keygen -t ed25519-sk) rather than on this computer (-t ed25519). Refused where git already signs with a key on this computer: it says how to switch. |
Exit codes: 0 the key is ready, and registered, or registered already, or the organisation is not known yet; 1 refused or failed, or you are not an Owner or Admin and an Owner or Admin must register it; 2 asked wrongly, such as an --organisation that is not a namespace.
meridian plugin release¶
meridian plugin release <version> [--registry community|verified] [--organisation <namespace>] [--dir <dir>] [--timeout <d>] [--details]
Releases the plugin in --dir: it checks, signs the tag on main's head and publishes it. It opens no pull request and changes no file. Changed in CLI 0.1.49.
The version is raised in the change's own pull request, before the release: that pull request sets pyproject.toml's version to <version> and adds the change to CHANGELOG.md under a heading for it, ## <version>, with no date and no "(unreleased)", since the date lives on the tag and the listing. Once it is merged and main's CI is green:
- Checks. It changes nothing if one fails, and each refusal says what to do next. The checkout is on the repository's default branch, holds no change git does not, and is at
origin's head;<version>is higher than the last release tag;pyproject.tomlat that head says<version>;CHANGELOG.mdthere has the heading## <version>alone; and that head's CI is green. A heading with a date or "(unreleased)" is refused, naming it; a CI still running is refused, to run it again once green. A leadingvon<version>is optional. - The tag. It signs
v<version>on that head with git's SSH signing key, and pushes it. - Publishing. It publishes the tag as
meridian plugin publishdoes, follows the build, and prints the listing's address.
Where v<version> is on origin already, signed and pushed by an earlier run whose publish did not finish, it publishes that tag and checks nothing else. Where a local tag of that name is on another commit, it is refused, naming both.
It reads the CI through GitHub's API with your own GitHub sign-in: GH_TOKEN or GITHUB_TOKEN, else the credential git keeps for github.com. Nothing else is installed for it, and the token is never printed or kept. Without git's SSH signing key, it stops before the tag and names meridian plugin key setup.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--registry |
community or verified |
community |
Where it is listed. |
Exit codes: 0 listed or in review, or still building when --timeout ran out; 1 a check stopped it, GitHub or git refused, or the publish was refused or failed; 2 asked wrongly, such as no version, or an origin that is not a repository on GitHub.
meridian plugin publish¶
meridian plugin publish --registry community|verified --tag <vX.Y.Z> [--organisation <namespace>] [--dir <dir>] [--timeout <d>] [--details]
Asks the platform to build a signed release tag of the plugin and list it. It builds, signs and pushes nothing itself: meridian plugin release does those, then publishes as this does. First it refuses here what the platform's builder would refuse:
- a
--tagthat is notvand a SemVer 2.0 version, or not the versionpyproject.tomlholds; - the plugin failing
meridian plugin check, with--verifiedfor the Verified registry; - a tag not signed with an SSH key (
git -c gpg.format=ssh tag -s v0.18.0); - an
originthat is not a repository on GitHub.
Then it asks for the build, and follows it until the version is listed, in review, or refused. From CLI 0.1.51 it sends the display name the plugin's code declares (meridian.Interface(title=...), written out or through a constant), and the platform's builder refuses a build whose image declares another: the refusal says so plainly, with both names, and the fix is to make the two agree and publish a new tag. A publish while two of the organisation's builds are running is queued, not refused: it says queued once, and is followed until it settles. A refusal with status 429 is the organisation's daily cap of publishes: nothing was built. It prints one line a check the platform names, ok, warn or FAIL, cut to the terminal's width (--details prints each check's full text), the build's public log, and the listing's address once it is listed. community lists once the checks pass; verified queues the version for a person's review, and lists nothing until it is approved. See Publish a plugin to a registry.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--registry |
community or verified |
none (required) | Where it is listed. verified is refused for an organisation without the verified grade. |
--tag |
<vX.Y.Z> |
none (required) | The release tag, signed with an SSH key registered on the organisation. The platform builds the commit it pins in the organisation's linked repository, so push it first. |
Exit codes: 0 listed or in review; 1 refused, here or by the platform, or failed; 2 asked wrongly.
meridian plugin key¶
meridian plugin key register <file.pub> [--organisation <namespace>]
meridian plugin key revoke <key-id> --reason <text> [--not-trusted-after <time>] [--organisation <namespace>]
meridian plugin key list [--organisation <namespace>]
The organisation's tag-signing keys at the platform, as its Signing keys page holds them. register sends an SSH public key (the platform holds public halves only), named by the key's comment or its file's name. revoke revokes one by its SGK- ID, with why; a tag it signed before --not-trusted-after still verifies. list reads them: each key's ID, name, fingerprint, when it was registered, and when it stopped being trusted. To make a key and register it in one step, see meridian plugin key setup.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--reason |
<text> |
none (required by revoke) |
Why, as the revocation records it. |
--not-trusted-after |
<time> |
now | When the key stopped being trusted, as 2026-10-16T12:00Z. Never later than now. |
Exit codes: 0 done; 1 refused or failed; 2 asked wrongly, such as a file that is no SSH public key or an ID that is not a signing key's.
meridian plugin download¶
meridian plugin download <namespace>/<plugin>@<version> [--registry community|verified] [--to <file>]
On a connected machine, writes a plugin bundle of a listed version for a deployment that reaches nothing: the image, its provenance, bill of materials, scan results and listing statement, the declaration and its documentation, and a dated copy of the signing keys' status. It verifies every digest and signature against the platform's keys this release of the CLI carries, and writes nothing unless every one verifies. CLI 0.1.52 carries the platform's registry-signing key, version 1.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--registry |
community or verified |
community |
Which registry's version. |
--to |
<file> |
<namespace>-<plugin>-<version>.plugin-bundle.tar |
Where the bundle goes. |
Exit codes: 0 written; 1 refused, failed, or not verified; 2 asked wrongly.
meridian plugin upload --bundle¶
Carries a plugin bundle into the deployment's catalogue, through the session meridian connect keeps. It verifies the bundle here first, against the platform's keys this release carries, as plugin download does; then the deployment verifies it against the platform's keys its chart carries, as at an install, and records the version with its listing and registry. See Install a plugin from a registry.
Exit codes: as for the other plugin commands on a deployment.
meridian plugin install¶
meridian plugin install <organisation>/<plugin>[@<version>] --note <why> [--registry community|verified]
meridian plugin install --pack <pack> --note <why> [--with <plugin>]... [--without <plugin>]... [--yes]
Added in CLI 0.1.51. Installs a version the registries list into the
connected deployment, as the dashboard's Browse does, as its
deployment admin, through the session meridian connect keeps
(--deployment names another). It launches nothing: launch each
version afterwards with meridian plugin launch,
approving the roles it asks for.
- A listing.
<organisation>/<plugin>@<version>installs that version, from--registrywhere given, the Community registry otherwise. Without@versionit installs the one Browse shows: the newest offered release, verified first. - A starter pack. Changed in CLI 0.1.53:
--pack <pack>installs a starter pack through the deployment, as Browse's install sheet does: one pack install, which the deployment keeps, one record naming the pack's revision, the members and how each went, and each member's own install record naming its pack. A bare name isopen-meridian's:cryptoisopen-meridian/crypto. The deployment resolves each member to its newest offered release at or above the pack's minimum for it; one with none offered is unavailable, and one the deployment will not take (its floor not met, or a registry it does not take) is not permitted here: neither is installed, and a pack whose required member is either is not. The members the pack's curator preselected are installed, and the members to choose are not:--with <plugin>adds one, and--without <plugin>leaves out a preselected one, each repeatable, by its listing or its plugin part. A name that is not a member is refused before anything is installed. It needs a deployment whose core is at chart 0.1.313; an older one is said in one line. - Asked first. The plan is shown version by version, and asked about
before anything is installed. At a terminal, each member to choose that
you did not name is asked about too.
--yesapproves the plan without asking, and installs no member to choose you did not name; where there is no terminal, nothing is installed without it. - A member already held is never moved: at or above the pack's version
it counts as installed, and below it the line says
Installed 0.3.0; 0.5.1 offered. --noteis required: the deployment keeps it with each install.
The install runs in the deployment, member by member; the command follows
it, one line a member as each ends, going on past a failure, then lists
meridian plugin launch for each version ready to launch and each that
failed, with why. A listing without @version reads the platform's
catalogue through your platform sign-in, and a platform that does not yet
let the CLI read its catalogue says so in one line; --pack reads only the
deployment.
| Flag | Argument | Meaning |
|---|---|---|
--note |
<why> |
Why, kept with each install. Required. |
--registry |
community or verified |
A listing's registry. Default: with @version, community; without, the one Browse shows. Not with --pack, whose members name their own. |
--pack |
<pack> |
A starter pack, organisation/pack, or a bare name for open-meridian's. |
--with |
<plugin> |
With --pack: also install this member to choose. Repeatable. |
--without |
<plugin> |
With --pack: leave out this preselected member. Repeatable. |
--yes |
Install without being asked. Where there is no terminal, nothing is installed without it. |
Exits 0 when every member asked for is installed or held; 1 when one failed, each named, or nothing was approved. See Install a plugin from a registry.
meridian plugin pack¶
meridian plugin pack list [--json]
meridian plugin pack show <pack> [--json]
meridian plugin pack update <pack> --note <why> [--yes] [--json]
meridian plugin pack remove <pack> --note <why> [--yes] [--json]
Added in CLI 0.1.53. Reads and keeps the starter packs a deployment has
installed, through core's pack routes, as the deployment admin, through the
session meridian connect keeps (--deployment names another):
list: Browse's pack view, one line a pack, its title, asset classes, members and its state here.show <pack>: the pack's page, its setup checklist: each member's versions, its state with why, its connection and the step after it, what a revision added, and the launch command for each member installed and not launched.update <pack>: Update all: each launched member moved to its newest offered version, an installed one given it, and one asking for more roles left for its own approval. What would move is shown first.remove <pack>: removes only the deployment's record of the pack. Every member stays installed, and running where it runs; they are shown first.
update and remove take --note, which the deployment keeps with the
pack's record, and are asked about unless --yes; where there is no
terminal, nothing is done without it. --json prints the deployment's
answer as core's starter pack tools give it:
a pack's state and said, each member's state, and its setup_href
where core gives one. The commands need a deployment whose core is at chart 0.1.313; an older one is said in one line.
Exits 0 when done; 1 when refused or not approved; 2 when asked wrongly; 3 without a session.
meridian plugin gc¶
Collects the garbage of the deployment's own plugin registry: the layers that versions retired from the catalogue left behind. It never deletes a manifest, only layers no version's manifest names. It works with your own cluster rights, through kubectl, as backup does.
- Check. It changes nothing if one fails: the deployment brings a plugin registry of its own, and you may patch StatefulSets and exec into pods in the namespace.
- Show and ask. It prints the three steps below, and asks
Collect it?. Anything butyoryesis no. With no terminal to ask at, it is refused unless you pass--yes. - Collect. It sets the registry read-only and restarts it: uploads wait, and pulls go on, so running plugins and launches are untouched. It runs the registry's garbage collection in its pod, then sets the registry back and restarts it, and prints what was collected.
A run stopped half-way leaves the registry read-only, and says so. The next run finds it so and finishes. Where the collection itself fails, it sets the registry back first.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--release |
<name> |
meridian |
The Helm release. |
--timeout |
<d> |
5m |
How long to give the registry each restart. |
--yes |
off | Collect without being asked. For a script that has already read what it will do. | |
-n, --namespace |
<name> |
meridian |
The namespace. |
Exit codes: 0 collected, and the registry takes uploads again; 1 a check stopped it (nothing was changed), it was not approved, or it failed, saying how the registry was left; 2 asked wrongly.
meridian upgrade¶
Replaces this binary with the latest release, or with the release --to names, older or newer. It upgrades the command line, not a deployment: that is meridian upgrade-deployment. Releases come from https://github.com/open-meridian/meridian-cli/releases, or from MERIDIAN_RELEASES, which must be HTTPS unless it is this machine. The download is checked against the .sha256 published beside it. This catches a broken download, not a compromised release; signing is not built yet.
It never looks for a newer release on its own.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--to |
<version> |
the latest release | A named release. A leading v is optional. |
Exit codes: 0 replaced, or already that version; 1 failed, for example when the binary's directory is not writable. That check happens before anything is downloaded.
meridian uninstall¶
Removes the command line. It lists what it will remove and asks first:
- Every delegation it holds, revoked at each deployment. One with a deployment it can't reach is forgotten here: it says when the delegation lapses, which you can bring forward by revoking it from Connected clients.
- The sessions directory.
- Its sign-ins to the platform's registries, forgotten here; each lapses at the platform within 30 days.
- This machine's certificate authority, where
meridian authoritymade one. - The binary.
| Flag | Argument | Default | Meaning |
|---|---|---|---|
--yes |
off | Remove without being asked. With no terminal to ask at, it is refused without this. |
Exit codes: 0 removed, 1 not approved or failed. If the binary's directory is not writable, it stops before revoking or ending anything.
meridian --version¶
Prints meridian <version> (<target>; meridian-core <revision>). meridian version is the same.
Environment variables¶
| Variable | Read by | Meaning |
|---|---|---|
MERIDIAN_ENROLMENT_CODE |
up |
The enrolment code, when --enrolment-code is not given. |
MERIDIAN_FIRST_RUN_CODE |
up --params |
The first-run claim code, when --first-run-code is not given. |
MERIDIAN_<FIELD> |
up --params |
Each credential field the wizard asks for, by its field name in capitals. |
MERIDIAN_RELEASES |
upgrade |
Where releases are fetched from, in place of GitHub. |
GH_TOKEN, GITHUB_TOKEN |
plugin release |
Your GitHub sign-in, which reads main's CI before the tag is signed. Without either, the credential git keeps for github.com. |
COLUMNS |
plugin release, plugin publish |
The terminal's width each check's line is cut to. |
XDG_CONFIG_HOME, APPDATA, HOME |
session commands | Where the sessions directory is. |
MERIDIAN_INSTALL_DIR, MERIDIAN_VERSION |
install.sh |
Where the install script puts the binary (default ~/.local/bin), and which release it installs. |