mxR 0.7.1 · Documentation · updated 8 November 2026
Raise it, approve it, deploy it one site at a time, inspect it, close it.
mxR is the CI/CD/CT command line for Maximo Application Suite Manage. This page is the whole of it: the full run on a live Manage (Manage 9.2.237), command by command with the real output, then the reference for the path rule, work orders, job plans, the agent, tests, failure codes, security, promotion from one site to another the version history that needs no Git and the thirteen commands.
Your repo folder is the source of truth, with Git or without. Deployed state lives as annotations on the ManageWorkspace, secrets come from environment variables, one API key per environment covers hot changes, and every command is idempotent and resumable.
Built now
Five Maximo nouns, real semantics.
mxR treats every Maximo change the way Maximo treats work. Nothing else gets Maximo vocabulary.
| Pipeline thing | Maximo noun | What it carries |
|---|---|---|
| A change set or release | Work Order | WAPPR → APPR → INPRG → COMP → CLOSE, or CAN. The work type (HOT, RESTART, REBUILD) comes from the path rule. A REBUILD carries a window and a backup reference before approval. |
| An environment | Site | DEV, TEST, PROD. The same work order is worked on one site at a time; promotion is start on the next site with the same commit, or mxr promote plan from one site to another. |
| The pipeline and the tests | Job Plan | An ordered task list. Each task has On, Gate and Test toggles, a budget and app tags. This is what the board edits. |
| A schedule | PM | Raises an inspection work order on a cron: nightly drift, weekly full UI, perf. |
| A failure | Failure code | One field on the work order: BUILD/COMPILE, BUILD/DBC, DEPLOY/CR-NOTREADY, DEPLOY/HOT-PARTIAL, DBC/UPDATEDB, TEST/GATE, TEST/UI-FLAKY, AUTH/INFRA. |
- 01 · One source of truth — The repo folder. Nothing is configured in a UI that is not a file in the repo. mxr.yaml, sites/<SITE>.yaml, jobplans/*.yaml, archive/ and hot/ are the whole configuration. Git keeps its history; without Git, mxR keeps it in .mxr/history.
- 02 · Deployed state — On the ManageWorkspace. What is deployed lives as annotations on the CR: mxr.io/git-sha (the commit, or the saved version id without Git), mxr.io/archive-sha, mxr.io/backup-ref, mxr.io/deployed-at. A site without a CR takes the sha of its last completed work order.
- 03 · One key, one agent — MXR.AGENT. One Jython autoscript (0.3.1), installed and updated on every site through the Automation Scripts object structure by the same API key the customer gives mxR. It gives the CLI Migration Manager, presentations, cron run-now, workflow initiate and a compile on the site's own interpreter as script endpoints.
| Ran live on | What ran | When |
|---|---|---|
| Manage 9.2.232 (MAS 9.2, Liberty 26.0.0.7, DB2) | init, doctor, export, plan, dry run, start with lint on the site's Jython, deploy, smoke, api, script endpoints, UI journeys and drift; inspect, PM, Tekton render; a gate failure and cancel with its runbook; plan from the last completed work order | 29 to 31 October 2026 |
| Manage 9.2.237 (MAS 9.2, DB2) with Maximo for Aviation and Service Provider | the same lifecycle; doctor's test-site check; virtual records; Work Order Tracking on the Aviation form; the Technician mobile journey; golden screens: 286 applications captured at 1.6 s each and all 286 checked in 8 min 15 s; golden journeys: all 287 screens captured with their journeys in 36 minutes, 894 generated tests (235 search dialogs, 258 query menus, 114 records with 783 tabs and sub-tabs), the whole set replayed by one weekly work order in 50 minutes | 31 October to 1 November 2026 |
Built now
Install mxR, then connect your first site.
mxR is free (Apache-2.0) and installs with npm. You need Node 22 LTS (20 still runs), the URL of your Manage site and one API key for it.
Install · Windows
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgzNeed Node 22? winget install OpenJS.NodeJS.LTS, then open a new PowerShell window.
Install · macOS
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgzNeed Node 22? brew install node.
Install · Linux
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgzNeed Node 22? Get it from nodejs.org or your package manager.
A site file needs only the Manage URL. The key comes from MXR_<SITE>_APIKEY or MXR_APIKEY, the browser login from MXR_<SITE>_UI_USER and _PASSWORD when the UI pack runs, and the test org and site from whoami when left out. --site is optional while mxr.yaml has one site.
From an empty repo to a completed work order · Windows
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgz # free, Apache-2.0. Node 22 LTS (20 still runs)
$env:MXR_DEV_APIKEY = [Net.NetworkCredential]::new('', (Read-Host 'API key' -AsSecureString)).Password # paste the key: kept in this session, out of history
mxr init --site DEV --url https://<host>/maximo # mxr.yaml, sites/DEV.yaml and four job plans
mxr doctor # the key, the agent, the grants; cluster checks switch on when a site names one
mxr export --all # the live site becomes the baseline; with Git, commit it: git add -A && git commit -m "mxr bootstrap"
# change something under hot/ (with Git, commit it), then:
mxr plan # the diff decides the tier and raises WO1001
mxr start WO1001 # deploy, job plan, COMP on DEV
mxr start WO1001 --site UAT --backup-ref db2-2026-10-31 # next environment: same work order, same version
mxr promote plan --from DEV --to UAT # or raise one from two sites: what differs, approved as one list
mxr ui # the board and the Work Order screen on localhost
mxr history list # no Git needed: every version mxr recorded, and what changedFrom an empty repo to a completed work order · macOS
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgz # free, Apache-2.0. Node 22 LTS (20 still runs)
read -rs MXR_DEV_APIKEY && export MXR_DEV_APIKEY # paste the key: the only secret, kept in the environment and out of shell history
mxr init --site DEV --url https://<host>/maximo # mxr.yaml, sites/DEV.yaml and four job plans
mxr doctor # the key, the agent, the grants; cluster checks switch on when a site names one
mxr export --all # the live site becomes the baseline; with Git, commit it: git add -A && git commit -m "mxr bootstrap"
# change something under hot/ (with Git, commit it), then:
mxr plan # the diff decides the tier and raises WO1001
mxr start WO1001 # deploy, job plan, COMP on DEV
mxr start WO1001 --site UAT --backup-ref db2-2026-10-31 # next environment: same work order, same version
mxr promote plan --from DEV --to UAT # or raise one from two sites: what differs, approved as one list
mxr ui # the board and the Work Order screen on localhost
mxr history list # no Git needed: every version mxr recorded, and what changedFrom an empty repo to a completed work order · Linux
npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgz # free, Apache-2.0. Node 22 LTS (20 still runs)
read -rs MXR_DEV_APIKEY && export MXR_DEV_APIKEY # paste the key: the only secret, kept in the environment and out of shell history
mxr init --site DEV --url https://<host>/maximo # mxr.yaml, sites/DEV.yaml and four job plans
mxr doctor # the key, the agent, the grants; cluster checks switch on when a site names one
mxr export --all # the live site becomes the baseline; with Git, commit it: git add -A && git commit -m "mxr bootstrap"
# change something under hot/ (with Git, commit it), then:
mxr plan # the diff decides the tier and raises WO1001
mxr start WO1001 # deploy, job plan, COMP on DEV
mxr start WO1001 --site UAT --backup-ref db2-2026-10-31 # next environment: same work order, same version
mxr promote plan --from DEV --to UAT # or raise one from two sites: what differs, approved as one list
mxr ui # the board and the Work Order screen on localhost
mxr history list # no Git needed: every version mxr recorded, and what changed- 01 · init — Two inputs, four files. mxr.yaml with the site and its gates, sites/DEV.yaml with the URL and the test scope, the four job plans (standard-hot, full-rebuild, drift, weekly-ui) and a .gitignore for .mxr/.
- 02 · doctor — Prove the key and the test site. whoami, the agent installed or updated (the newer of the repo's copy and the CLI's), the key's groups and the Automation Scripts grants the agent needs, then one work order created and cancelled on the test site. Cluster, bucket and admin-image checks run for the sites that name them.
- 03 · export — See your system. Scripts with launch points and variables, presentations and the Migration Manager package list land in hot/ with stable names and sorted keys, following the site's export filters, so git diff is meaningful.
Built now
A change is a work order.
The statuses are Maximo's own, the records are not: an mxR work order, job plan or PM lives in your repo folder and in the run record under .mxr/runs, never as a record in Maximo. Transitions are a table; an illegal one throws. The same work order moves from site to site: promotion is mxr start again on the next site, same commit.
| Status | Reached by | What it means |
|---|---|---|
| WAPPR | mxr plan or mxr promote plan | raised from what changed (a Git diff, or the difference between two saved versions), or from what differs between two sites, with the job plan for its tier |
| APPR | mxr approve | or self-approved and recorded where the site has no approval gate |
| INPRG | mxr start | deploy for the tier, then every task in the job plan |
| COMP | gated tasks pass | the same work order starts on the next site, same commit |
| CAN | mxr cancel --code | one failure code and the recovery runbook, filled in |
A gated failure holds INPRG. The same command resumes it, skipping the tasks that passed; mxr cancel closes it with one failure code and prints the runbook.
Built now
Init: two inputs, four files.
A site name and the Manage URL. With the API key already exported, init reads the key's user and fills in where that user creates test records. Everything else has a default. On this lab the test scope was pinned to the key user's own org and site, the only edit made by hand. Every command from here to step 15 ran against Manage 9.2.237 on 2026-10-31; the output is the terminal's, with the host, the tenant and the operator masked.
$ mxr init --site DEV --url https://manage.example.com/maximo
✓ wrote mxr.yaml
✓ wrote sites/DEV.yaml
✓ wrote jobplans/drift.yaml
✓ wrote jobplans/full-rebuild.yaml
✓ wrote jobplans/standard-hot.yaml
✓ wrote jobplans/weekly-ui.yaml
✓ wrote .gitignore
✓ MXR_DEV_APIKEY works: MXINTADM, tests on org EAGLENA site BEDFORD
next: mxr doctor → mxr export → git add -A && git commit -m "mxr bootstrap"$ cat mxr.yaml
# mxr.yaml — the repo is the only source of truth. Nothing is configured in a UI that is not a file here.
# Add a site per environment; gates: [] (none), [backup], [backup, window, approval] (PROD).
sites:
DEV: { gates: [] }
# Who may approve (git user.name or MXR_ACTOR). Empty: anyone who runs mxr approve.
approvers: []
pms:
- name: nightly-drift
cron: 0 2 * * *
jobplan: drift
site: DEV$ cat sites/DEV.yaml
# sites/DEV.yaml — one Manage site. The key comes from the env var MXR_DEV_APIKEY (or MXR_APIKEY).
baseUrl: https://manage.example.com/maximo
# For REBUILD and RESTART, name the ManageWorkspace (HOT deploys and tests need only the key):
# cluster: { namespace: mas-inst1-manage, workspace: inst1-dev }
# Where tests create records (cancel/close, never delete): the key's user's default org and insert site.
test: { org: EAGLENA, site: BEDFORD, allowCreate: true }
# Browser login for the UI pack: MXR_DEV_UI_USER and MXR_DEV_UI_PASSWORD are used when set.
# Everything else (timeouts, archives, cr overlay, mif, export filters) has a default: see templates/sites/SITE.yaml.
# Which live artefacts `mxr export --all` pulls: the ones this repo owns.
export: { scripts: 'autoscript like "MXR%"' }Built now
Doctor: the key, the agent, the grants.
Doctor installs MXR.AGENT through the Automation Scripts object structure, asks it who is calling, then creates one work order on the test site and cancels it, so a site that refuses the records the tests create is found here, with the attribute named, before any gate. No flag needed when mxr.yaml has one site. Cluster, bucket and admin-image checks switch themselves off for a site that does not use them.
$ mxr doctor
· cluster checks: site has no cluster: block
✓ Manage apikey: whoami answers as MXINTADM
✓ MXR.AGENT: unchanged (version 0.3.1, from the CLI)
✓ whoami.extended: Manage 9.2.237 · db2 · bundle all · MAXUPG V9200-175
✓ API key user MXINTADM: in ASSISTADMIN, ASSISTSTUDIOUSERS, ASSISTTECHUSERS, EVERYONE, ITEMMGR, LICENSEUSAGEVIEW, MAFCONFIGADMIN, MAXADMIN, MOBADMINAPPADMIN, MONITORINT, MONITOR_ADMIN, MONITOR_READ_ONLY, MONITOR_USERS, MREFINT, OPTIMIZERADMIN, OPTIMIZERUSERS, STOREROOMCLERK, TOOLMGR, USERMANAGEMENT
✓ Automation Scripts grants for the key's user: ATTRVAL, BMXVIEWMANAGEWHER, BMXVIEWMANAGEWHERRO, BOOKMARK, CFGACTN, CFGATTR, CFGCCOND, CFGIFACE, CFGOBJECT, CFGSCRIPT, CLEAR, CREATEKPI, DELETE, DELMCPTOOL, DUPLICATE, HIDEATTR, HIDEBUT, HIDEDEL, HIDEIMP, HIDELPATTR, IFACEESDIRRQ, IFACEESDIRRS, IFACEESIC, IFACEICDIRRQ, IFACEICDIRRS, IFACEPCESIC, IFACETYPE, IFACETYPEES, IFACETYPEIC, IFACETYPENO, IFACETYPEPC, IFACETYPEROLE, IFACEXIT, IFEXPORT, IFIMPORT, INTSCRIPT, LAUNCH, LPATTRVAL, MANMCPTOOL, NEXT, PREVIOUS, READ, RUNREPORTS, RUNSCRIPT, SAVE, SCRIPTCRON, SCRIPTEP, SCRIPTERR, SCRIPTFORM, SCRIPTIN, SCRIPTNOOBJ, SCRIPTOTHER, SCRIPTOUT, SCRIPTREST, SCRIPTWHERE, SEARCHBOOK, SEARCHMORE, SEARCHSQRY, SEARCHTIPS, SEARCHVMQR, SEARCHWHER recorded for ASSISTADMIN, ASSISTSTUDIOUSERS, ASSISTTECHUSERS, EVERYONE, ITEMMGR, LICENSEUSAGEVIEW, MAFCONFIGADMIN, MAXADMIN, MOBADMINAPPADMIN, MONITORINT, MONITOR_ADMIN, MONITOR_READ_ONLY, MONITOR_USERS, MREFINT, OPTIMIZERADMIN, OPTIMIZERUSERS, STOREROOMCLERK, TOOLMGR, USERMANAGEMENT, and the agent install worked, so the key has what it needs
✓ test site BEDFORD: a work order creates and cancels (1290)
· bucket: no bucket in mxr.yaml (needed for REBUILD)
· admin image pull: no cluster.adminImage in the site file (needed for Java builds)
all checks passedManage 9.2.237 · db2 · bundle all · agent 0.3.1. The agent reads only hot artefact tables, writes only to them, leaves MAXADMIN and every security property alone, and logs every call, whoever holds the key.
Built now
Export: the live site becomes the repo.
Each artefact comes down as a file pair with sorted keys, so a later difference is drift: git diff with Git, mxr history diff without. This run pulled scripts; --only takes any list of artefact types and --all follows the site's export filters.
$ mxr export --all --only scripts
scripts 2 file(s)
exported 2 file(s) from DEV
drift (git status --porcelain -- hot):
?? hot/$ tree -L 2 (after export)
.
hot
hot/scripts
jobplans
sites
hot/scripts: 2 files, e.g. MXR.AGENT.py MXR.AGENT.yamlBuilt now
The change: a script endpoint with its own test.
A Jython script endpoint and its sidecar. The sidecar carries a tests: block, which the job plan's scripts-test task runs after the deploy. The Playwright pack sits under tests/.
$ git log --oneline --stat -2
1e3e793 MXR_DEMO_HELLO: script endpoint with its test; mif off (no WireMock on this lab)
.gitignore | 1 +
hot/scripts/MXR_DEMO_HELLO.py | 4 +
hot/scripts/MXR_DEMO_HELLO.yaml | 10 +
jobplans/standard-hot.yaml | 2 +-
tests/api/playwright.config.ts | 17 ++
tests/api/wodetail.spec.ts | 31 ++++
tests/ui/fixtures/auth.ts | 110 +++++++++++
tests/ui/fixtures/controls.ts | 34 ++++
tests/ui/fixtures/global-setup.ts | 31 ++++
tests/ui/fixtures/manage-page.ts | 273 ++++++++++++++++++++++++++++
tests/ui/fixtures/seed.ts | 105 +++++++++++
tests/ui/journeys/asset.spec.ts | 37 ++++
tests/ui/journeys/po.spec.ts | 34 ++++
tests/ui/journeys/technician-mobile.spec.ts | 17 ++
tests/ui/journeys/wotrack.spec.ts | 59 ++++++
tests/ui/playwright.config.ts | 39 ++++
16 files changed, 803 insertions(+), 1 deletion(-)
623ab66 mxr bootstrap: site DEV, exported scripts
.gitignore | 2 +
hot/scripts/MXR.AGENT.py | 1296 ++++++++++++++++++++++++++++++++++++++++++++
hot/scripts/MXR.AGENT.yaml | 9 +
jobplans/drift.yaml | 6 +
jobplans/full-rebuild.yaml | 18 +
jobplans/standard-hot.yaml | 18 +
jobplans/weekly-ui.yaml | 7 +
mxr.yaml | 6 +
sites/DEV.yaml | 14 +
9 files changed, 1376 insertions(+)$ cat jobplans/standard-hot.yaml
# jobplans/standard-hot.yaml — the pipeline and the tests, as data. Edit it with `mxr ui` or by hand.
name: standard-hot
tasks:
- { id: lint, type: scripts, on: true, gate: true, test: true } # py_compile + GraalJS parse
- { id: dbc, type: dbc, on: true, gate: true, test: true } # XML schema + per-target sql check
- { id: migmgr, type: config, on: true, gate: true, test: true } # package compare / preview
- { id: deploy, type: deploy, on: true, gate: true }
- { id: smoke, type: smoke, on: true, gate: true, budget: 5m }
- { id: api, type: api, on: true, gate: true, budget: 10m }
- { id: scripts-fn, type: scripts-test, on: true, gate: true, budget: 5m } # script endpoints + launch-point fire
- { id: cron, type: cron-test, on: true, gate: false, budget: 5m } # agent cron.runNow + CRONTASKHISTORY
- { id: esc, type: escalation, on: false, gate: false, budget: 5m }
- { id: wf, type: workflow, on: false, gate: false, budget: 8m }
- { id: mif, type: mif, on: false, gate: true, budget: 8m } # /meaweb/es + WireMock svc + MAXINTMSGTRK
- { id: ui, type: ui, on: true, gate: true, budget: 15m, tags: ["@smoke", "@WOTRACK"] }
- { id: mobile, type: ui-mobile, on: false, gate: false, budget: 10m } # one Technician journey
- { id: perf, type: perf, on: false, gate: false } # k6, report-only
- { id: drift, type: drift, on: true, gate: false } # report-onlyBuilt now
Plan: the diff decides the tier and raises the work order.
The path rule, nothing else: archive/** or a REBUILD key in the site file is a rebuild, another site-file key is a restart, everything else is hot. The work order is created WAPPR with the job plan the tier's template names, and the plan says what each path means.
$ mxr plan --from 623ab66
WO1001 WAPPR HOT commit 1e3e793a6929
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 16
tier HOT because:
HOT .gitignore (repo file: nothing to deploy)
HOT hot/scripts/MXR_DEMO_HELLO.py (goes in through REST)
HOT hot/scripts/MXR_DEMO_HELLO.yaml (goes in through REST)
HOT jobplans/standard-hot.yaml (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/api/playwright.config.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/api/wodetail.spec.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/fixtures/auth.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/fixtures/controls.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/fixtures/global-setup.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/fixtures/manage-page.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/fixtures/seed.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/journeys/asset.spec.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/journeys/po.spec.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/journeys/technician-mobile.spec.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/journeys/wotrack.spec.ts (tests and job plans: nothing to deploy, the job plan picks it up)
HOT tests/ui/playwright.config.ts (tests and job plans: nothing to deploy, the job plan picks it up)
next: mxr start WO1001 --site DEVBuilt now
Dry run: preview everything, change nothing.
The same job plan with every write held back. The record, the approval and the status stay exactly as they were; the preview shows what each deployer would create or update.
$ mxr start WO1001 --dry-run
WO1001 would be auto-approved by maximo.admin (no approval gate on DEV)
MXR.AGENT unchanged (0.3.1)
WO1001 HOT → DEV (standard-hot) [dry run]
▶ lint (scripts, gate, budget 2m)
✓ lint pass 2 scripts compile
▶ dbc (dbc, gate, budget 2m)
· dbc skip no archive/ in the repo
▶ migmgr (config, gate, budget 10m)
· migmgr skip nothing under hot/ to compare
▶ deploy (deploy, gate, budget 2h30m)
preview scripts MXR_DEMO_HELLO create jython, 0 launch point(s)
· scripts MXR_DEMO_HELLO skipped dry run
✓ deploy pass applied 0, unchanged/skipped 1
▶ smoke (smoke, gate, budget 5m)
✓ smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
▶ api (api, gate, budget 10m)
· api skip dry run: npx playwright test -c tests/api/playwright.config.ts --reporter=json
▶ scripts-fn (scripts-test, gate, budget 5m)
✓ scripts-fn pass 1 script test(s) passed
▶ cron (cron-test, budget 5m)
· cron skip no cron instances in hot/crontasks (or task.with.instances)
▶ ui (ui, gate, budget 15m)
· ui skip dry run: npx playwright test -c tests/ui/playwright.config.ts --reporter=json --project desktop --grep @smoke|@WOTRACK
▶ drift (drift, budget 10m)
✓ drift pass drift on DEV: 3 files changed, 1 insertion(+), 14 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff)
✓ [dry run] WO1001 would be COMP on DEV; nothing was deployed or recorded. Run: mxr start WO1001 --site DEV
WO1001 INPRG HOT commit 1e3e793a6929
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 16
site DEV: done
[G] lint scripts pass 2 scripts compile
[G] dbc dbc skip no archive/ in the repo
[G] migmgr config skip nothing under hot/ to compare
[G] deploy deploy pass applied 0, unchanged/skipped 1
[G] smoke smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
[G] api api skip dry run: npx playwright test -c tests/api/playwright.config.ts --reporter=json
[G] scripts-fn scripts-test pass 1 script test(s) passed
[ ] cron cron-test skip no cron instances in hot/crontasks (or task.with.instances)
[ ] esc escalation skip off in job plan
[ ] wf workflow skip off in job plan
[G] mif mif skip off in job plan
[G] ui ui skip dry run: npx playwright test -c tests/ui/playwright.config.ts --reporter=json --project desktop --grep @smoke|@WOTRACK
[ ] mobile ui-mobile skip off in job plan
[ ] perf perf skip off in job plan
[ ] drift drift pass drift on DEV: 3 files changed, 1 insertion(+), 14 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff)Built now
Start: deploy in order, run the job plan, close.
A site without an approval gate self-approves and records it. Deployers run in one hard-coded order. Then each task persists running, acts, persists its result; a gated failure holds the work order INPRG with a failure code, and the same command resumes it.
$ mxr start WO1001
WO1001 auto-approved by maximo.admin (no approval gate on DEV)
MXR.AGENT unchanged (0.3.1)
WO1001 HOT → DEV (standard-hot)
▶ lint (scripts, gate, budget 2m)
✓ lint pass 2 scripts compile
▶ dbc (dbc, gate, budget 2m)
· dbc skip no archive/ in the repo
▶ migmgr (config, gate, budget 10m)
· migmgr skip nothing under hot/ to compare
▶ deploy (deploy, gate, budget 2h30m)
pre-push export: 2 file(s) → ~/maximo-repo/.mxr/runs/WO1001/export-before/DEV
preview scripts MXR_DEMO_HELLO create jython, 0 launch point(s)
✓ scripts MXR_DEMO_HELLO applied
✓ deploy pass applied 1, unchanged/skipped 0
▶ smoke (smoke, gate, budget 5m)
✓ smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
▶ api (api, gate, budget 10m)
✓ api pass 2 passed
▶ scripts-fn (scripts-test, gate, budget 5m)
✓ scripts-fn pass 1 script test(s) passed
▶ cron (cron-test, budget 5m)
· cron skip no cron instances in hot/crontasks (or task.with.instances)
▶ ui (ui, gate, budget 15m)
✓ ui pass 2 passed
▶ drift (drift, budget 10m)
✓ drift pass drift on DEV: 3 files changed, 5 insertions(+), 6 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff)
✓ WO1001 COMP on DEV
WO1001 COMP HOT commit 1e3e793a6929
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 16
site DEV: done
[G] lint scripts pass 2 scripts compile
[G] dbc dbc skip no archive/ in the repo
[G] migmgr config skip nothing under hot/ to compare
[G] deploy deploy pass applied 1, unchanged/skipped 0
[G] smoke smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
[G] api api pass 2 passed
[G] scripts-fn scripts-test pass 1 script test(s) passed
[ ] cron cron-test skip no cron instances in hot/crontasks (or task.with.instances)
[ ] esc escalation skip off in job plan
[ ] wf workflow skip off in job plan
[G] mif mif skip off in job plan
[G] ui ui pass 2 passed
[ ] mobile ui-mobile skip off in job plan
[ ] perf perf skip off in job plan
[ ] drift drift pass drift on DEV: 3 files changed, 5 insertions(+), 6 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff)$ curl -s -H 'apikey: <APIKEY>' -X POST 'https://manage.example.com/maximo/api/script/MXR_DEMO_HELLO'
{"hello":"mxR","serverTime":"Sat Oct 31 12:44:08 GMT 2026"}$ python3 -c import json;d=json.load(open('.mxr/runs/WO1001/run.json'));print(json.dumps({k:d[k] for k in d if k in ('wo','status','tier','commit','jobplan','approvals','sites','failureCode','history')},indent=1)[:9000])
{
"wo": "WO1001",
"status": "COMP",
"approvals": [
{
"by": "maximo.admin",
"at": "2026-10-31T12:42:58.168Z",
"signed": false,
"signature": "3a8b58be77107782a7b670539a5e8aa8aeedb6100154edad434ddd01f4d7a266",
"auto": true
}
],
"sites": {
"DEV": {
"site": "DEV",
"status": "done",
"tasks": [
{
"id": "lint",
"type": "scripts",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:42:58.468Z",
"attempt": 1,
"budgetMs": 120000,
"message": "2 scripts compile",
"details": [
{
"file": "MXR.AGENT.py",
"ok": true,
"tool": "jython on the site (MXR.AGENT script.compile)"
},
{
"file": "MXR_DEMO_HELLO.py",
"ok": true,
"tool": "jython on the site (MXR.AGENT script.compile)"
}
],
"endedAt": "2026-10-31T12:42:58.895Z"
},
{
"id": "dbc",
"type": "dbc",
"gate": true,
"on": true,
"status": "skip",
"startedAt": "2026-10-31T12:42:58.896Z",
"attempt": 1,
"budgetMs": 120000,
"message": "no archive/ in the repo",
"endedAt": "2026-10-31T12:42:58.897Z"
},
{
"id": "migmgr",
"type": "config",
"gate": true,
"on": true,
"status": "skip",
"startedAt": "2026-10-31T12:42:58.898Z",
"attempt": 1,
"budgetMs": 600000,
"message": "nothing under hot/ to compare",
"endedAt": "2026-10-31T12:42:58.902Z"
},
{
"id": "deploy",
"type": "deploy",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:42:58.903Z",
"attempt": 1,
"budgetMs": 9000000,
"message": "applied 1, unchanged/skipped 0",
"details": [
{
"type": "scripts",
"name": "MXR_DEMO_HELLO",
"path": "hot/scripts/MXR_DEMO_HELLO.py",
"status": "applied",
"ms": 462,
"response": {
"action": "created",
"version": "f575053ec29f",
"status": 200
}
}
],
"endedAt": "2026-10-31T12:42:59.633Z"
},
{
"id": "smoke",
"type": "smoke",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:42:59.634Z",
"attempt": 1,
"budgetMs": 300000,
"message": "whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))",
"details": [
{
"name": "whoami.extended",
"ok": true,
"detail": "MXINTADM on Manage 9.2.237 (db2, bundle all)"
}
],
"endedAt": "2026-10-31T12:42:59.759Z"
},
{
"id": "api",
"type": "api",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:42:59.760Z",
"attempt": 1,
"budgetMs": 600000,
"message": "2 passed",
"details": {
"passed": 2,
"failed": 0,
"flaky": 0,
"skipped": 0,
"failures": []
},
"endedAt": "2026-10-31T12:43:02.607Z"
},
{
"id": "scripts-fn",
"type": "scripts-test",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:43:02.610Z",
"attempt": 1,
"budgetMs": 300000,
"message": "1 script test(s) passed",
"details": [
{
"script": "MXR_DEMO_HELLO",
"test": "answers with the server time",
"ok": true,
"detail": "POST /api/script/MXR_DEMO_HELLO \u2192 200"
}
],
"endedAt": "2026-10-31T12:43:02.697Z"
},
{
"id": "cron",
"type": "cron-test",
"gate": false,
"on": true,
"status": "skip",
"startedAt": "2026-10-31T12:43:02.700Z",
"attempt": 1,
"budgetMs": 300000,
"message": "no cron instances in hot/crontasks (or task.with.instances)",
"endedAt": "2026-10-31T12:43:02.702Z"
},
{
"id": "esc",
"type": "escalation",
"gate": false,
"on": false,
"status": "skip",
"message": "off in job plan"
},
{
"id": "wf",
"type": "workflow",
"gate": false,
"on": false,
"status": "skip",
"message": "off in job plan"
},
{
"id": "mif",
"type": "mif",
"gate": true,
"on": false,
"status": "skip",
"message": "off in job plan"
},
{
"id": "ui",
"type": "ui",
"gate": true,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:43:02.703Z",
"attempt": 1,
"budgetMs": 900000,
"message": "2 passed",
"details": {
"passed": 2,
"failed": 0,
"flaky": 0,
"skipped": 0,
"failures": []
},
"endedAt": "2026-10-31T12:44:07.113Z"
},
{
"id": "mobile",
"type": "ui-mobile",
"gate": false,
"on": false,
"status": "skip",
"message": "off in job plan"
},
{
"id": "perf",
"type": "perf",
"gate": false,
"on": false,
"status": "skip",
"message": "off in job plan"
},
{
"id": "drift",
"type": "drift",
"gate": false,
"on": true,
"status": "pass",
"startedAt": "2026-10-31T12:44:07.113Z",
"attempt": 1,
"budgetMs": 600000,
"message": "drift on DEV: 3 files changed, 5 insertions(+), 6 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff)",
"details": {
"drifted": true,
"stat": "/{dev/null => tmp/mxr-drift-BS5XQR/hot/migmgr/packages.yaml} | 1 +\n .../guide-run2 => mxr-drift-BS5XQR}/hot/scripts/MXR_DEMO_HELLO.py | 2 +-\n .../hot/scripts/MXR_DEMO_HELLO.yaml | 8 +++-----\n 3 files changed, 5 insertions(+), 6 deletions(-)",
"errors": [],
"report": "~/maximo-repo/.mxr/runs/WO1001/drift-DEV.diff"
},
"endedAt": "2026-10-31T12:44:07.681Z"
}
],
"startedAt": "2026-10-31T12:42:58.467Z",
"hot": {
"items": [
{
"type": "scripts",
"name": "MXR_DEMO_HELLO",
"path": "hot/scripts/MXR_DEMO_HELLO.py",
"status": "applied",
"ms": 462,
"response": {
"action": "created",
"version": "f575053ec29f",
"status": 200
}
}
],
"exportBefore": "~/maximo-repo/.mxr/runs/WO1001/export-before/DEV"
},
"environment": {
"components": {
"APPCONFIG": "Application administration",
"PLANS": "Planning",
"MASMVI": "Maximo Visual Inspection",
"PLUSGCHG": "Change",
"PLUSAFLB": "Flight Operations",
"ANALYTICS": "Analytics",
"MASOPTIM": "Optimizer",
"PLUSTDTIMP": "Data Import (Tr)",
"SSDR": "Self Service",
"CONTRACT": "Contracts",
"PLUSGSD": "Safety and Quality Management",
"SLA": "Service Level",
"MON_SETUP": "Setup",
"FINANCIAL": "Financial",
"INT": "Integration",
"PURCHASE": "Purchasing",
"PLUSTCONST": "Consists (Tr)",
"SD": "Service Desk",
"MON_DB": "Dashboard",
"ENVIRON": "Environmental",
"SCHEDULER": "Planning and Scheduling",
"WO": "Work Orders",
"CHANGE": "Change",
"HEALTH": "Health",
"PLUSTWARR": "Warranties",
"MASASSIST": "Assist",
"PLUSPSP": "Service Provider",
"UTIL": "System Configuration",
"CI": "IT Infrastructure",
"BIM": "Building Information Models",
"ASSET": "Aircraft and Equipment",
"PLUSMEO": "Exchange Orders",
"PLUSTMP": "Motor Pool (Tr)",
"RELEASE": "Release",
"SETUP": "Administration",
"AVIATION": "Configuration Management",
"INVENTOR": "Inventory",
"AUTH": "Access and usage",
"SECURITY": "Security",
"PLUSGOP": "Operations",
"TM": "Task Management",
"CPI": "Performance monitoring and insights",
"IPC": "Navigator",
"PM": "Preventive Maintenance"
},
"javaVersion": "25.0.4.1",
"maxupg": "V9200-175",
"bundleType": "all",
"dbPlatform": "db2",
"manageVersion": "9.2.237",
"products": [
{
"varvalue": "V9200-175",
"varname": "MAXUPG"
}
],
"loginUserName": "MXINTADM",
"caller": {
"groups": [
"ASSISTADMIN",
"ASSISTSTUDIOUSERS",
"ASSISTTECHUSERS",
"EVERYONE",
"ITEMMGR",
"LICENSEUSAGEVIEW",
"MAFCONFIGADMIN",
"MAXADMIN",
"MOBADMINAPPADMIN",
"MONITORINT",
"MONITOR_ADMIN",
"MONITOR_READ_ONLY",
"MONITOR_USERS",
"MREFINT",
"OPTIMIZERADMIN",
"OPTIMIZERUSERS",
"STOREROOMCLERK",
"TOOLMGR",
"USERMANAGEMENT"
],
"user": "MXINTADM"
},
"adminMode": false,
"agentVersion": "0.3.1",
"serverTime": "Sat Oct 31 12:42:59 GMT 2026",
"probes": {
"mxe.db.driver": "ok",
"components": "ok",
"java.version": "ok",
"userInfo.getUserName": "ok",
"MAXVARS.MAXUPG": "ok",
"Version": "ok",
"mxe.db.url": "oBuilt now
Inspect: tests without a deploy.
An inspection work order runs a job plan against the site as it is. Here the nightly drift plan: a smoke test and an export-and-diff.
$ mxr inspect --jobplan drift
WO1002 INSPECT → DEV (drift)
▶ smoke (smoke, gate, budget 5m)
✓ smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
▶ drift (drift, budget 10m)
✓ drift pass drift on DEV: 3 files changed, 5 insertions(+), 6 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1002/drift-DEV.diff)
✓ WO1002 COMP
WO1002 COMP INSPECT commit 1e3e793a6929
job plan: drift (2 tasks on, 1 gated)
changed paths: 0
site DEV: done
[G] smoke smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
[ ] drift drift pass drift on DEV: 3 files changed, 5 insertions(+), 6 deletions(-) (report ~/maximo-repo/.mxr/runs/WO1002/drift-DEV.diff)
[ ] perf perf skip off in job planBuilt now
Schedule: a PM raises the inspection on a cron.
Preventive maintenance for the configuration. pm add writes the schedule into mxr.yaml and a Tekton CronJob next to it.
$ mxr pm add nightly-drift --cron 0 2 * * * --jobplan drift --site DEV
✓ PM nightly-drift: 0 2 * * * → mxr inspect --site DEV --jobplan drift
written mxr.yaml (pms) and tekton/pm-nightly-drift.yaml; commit both, then oc apply -f tekton/pm-nightly-drift.yaml$ mxr pm list
nightly-drift 0 2 * * * drift DEVBuilt now
Tekton: the same run as a PipelineRun.
One Pipeline, one Task that runs mxr. The render turns the repo and a job plan into a PipelineRun the cluster executes; each job plan task becomes a parameter and an mxr start --task step.
$ mxr pipeline render --site DEV --jobplan standard-hot --wo WO1001 --git-url https://git.example.com/acme/maximo.git --revision main --namespace mas-inst1-manage
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: mxr-dev-wo1001-
namespace: mas-inst1-manage
labels:
app.kubernetes.io/managed-by: mxr
mxr.io/site: dev
mxr.io/jobplan: standard-hot
spec:
params:
- name: wo
value: WO1001
- name: git-url
value: https://git.example.com/acme/maximo.git
- name: git-revision
value: main
- name: on-lint
value: "true"
- name: on-dbc
value: "true"
- name: on-migmgr
value: "true"
- name: on-deploy
value: "true"
- name: on-smoke
value: "true"
- name: on-api
value: "true"
- name: on-scripts-fn
value: "true"
- name: on-cron
value: "true"
- name: on-esc
value: "false"
- name: on-wf
value: "false"
- name: on-mif
value: "false"
- name: on-ui
value: "true"
- name: on-mobile
value: "false"
- name: on-perf
value: "false"
- name: on-drift
value: "true"
workspaces:
- name: source
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
taskRunTemplate:
serviceAccountName: mxr
pipelineSpec:
params:
- name: wo
type: string
default: ""
- name: git-url
type: string
- name: git-revision
type: string
default: main
- name: on-lint
type: string
default: "true"
- name: on-dbc
type: string
default: "true"
- name: on-migmgr
type: string
default: "true"
- name: on-deploy
type: string
default: "true"
- name: on-smoke
type: string
default: "true"
- name: on-api
type: string
default: "true"
- name: on-scripts-fn
type: string
default: "true"
- name: on-cron
type: string
default: "true"
- name: on-esc
type: string
default: "false"
- name: on-wf
type: string
default: "false"
- name: on-mif
type: string
default: "false"
- name: on-ui
type: string
default: "true"
- name: on-mobile
type: string
default: "false"
- name: on-perf
type: string
default: "false"
- name: on-drift
type: string
default: "true"
workspaces:
- name: source
tasks:
- name: clone
taskRef:
name: git-clone
kind: ClusterTask
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.git-revision)
workspaces:
- name: output
workspace: source
- name: t-lint
taskRef:
name: mxr
runAfter:
- clone
when:
- input: $(params.on-lint)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- lint
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-dbc
taskRef:
name: mxr
runAfter:
- t-lint
when:
- input: $(params.on-dbc)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- dbc
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-migmgr
taskRef:
name: mxr
runAfter:
- t-dbc
when:
- input: $(params.on-migmgr)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- migmgr
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-deploy
taskRef:
name: mxr
runAfter:
- t-migmgr
when:
- input: $(params.on-deploy)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- deploy
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-smoke
taskRef:
name: mxr
runAfter:
- t-deploy
when:
- input: $(params.on-smoke)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- smoke
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-api
taskRef:
name: mxr
runAfter:
- t-smoke
when:
- input: $(params.on-api)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- api
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-scripts-fn
taskRef:
name: mxr
runAfter:
- t-api
when:
- input: $(params.on-scripts-fn)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- scripts-fn
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-cron
taskRef:
name: mxr
runAfter:
- t-scripts-fn
when:
- input: $(params.on-cron)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- cron
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-esc
taskRef:
name: mxr
runAfter:
- t-cron
when:
- input: $(params.on-esc)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- esc
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-wf
taskRef:
name: mxr
runAfter:
- t-esc
when:
- input: $(params.on-wf)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- wf
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-mif
taskRef:
name: mxr
runAfter:
- t-wf
when:
- input: $(params.on-mif)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- mif
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-ui
taskRef:
name: mxr
runAfter:
- t-mif
when:
- input: $(params.on-ui)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- ui
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-mobile
taskRef:
name: mxr
runAfter:
- t-ui
when:
- input: $(params.on-mobile)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- mobile
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-perf
taskRef:
name: mxr
runAfter:
- t-mobile
when:
- input: $(params.on-perf)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- perf
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: source
- name: t-drift
taskRef:
name: mxr
runAfter:
- t-perf
when:
- input: $(params.on-drift)
operator: in
values:
- "true"
params:
- name: args
value:
- start
- WO1001
- --site
- DEV
- --task
- drift
- name: site
value: DEV
- name: secret
value: mxr-site-dev
workspaces:
- name: source
workspace: sourceBuilt now
A gate that bites: a change that does not compile stays off the site.
A third commit adds a script with a syntax error. The lint task compiles it on the site's own Jython through the agent, the gate fails, and the work order holds INPRG with TEST/GATE before the deploy task runs. The site is exactly as it was.
$ mxr plan
deployed sha for DEV: 1e3e793a6929 from WO1001 (the last completed work order; this site has no ManageWorkspace)
WO1003 WAPPR HOT commit 234be8b5f114
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 4
tier HOT because:
HOT hot/scripts/MXR_DEMO_BROKEN.py (goes in through REST)
HOT hot/scripts/MXR_DEMO_BROKEN.yaml (goes in through REST)
HOT mxr.yaml (repo file: nothing to deploy)
HOT tekton/pm-nightly-drift.yaml (repo file: nothing to deploy)
next: mxr start WO1003 --site DEV$ mxr start WO1003
WO1003 auto-approved by maximo.admin (no approval gate on DEV)
MXR.AGENT unchanged (0.3.1)
WO1003 HOT → DEV (standard-hot)
▶ lint (scripts, gate, budget 2m)
✗ lint fail 1 of 3 scripts fail to compile: MXR_DEMO_BROKEN.py (no viable alternative at input ':' (line 2))
✗ WO1003 stays INPRG on DEV: TEST/GATE — lint fail: 1 of 3 scripts fail to compile: MXR_DEMO_BROKEN.py (no viable alternative at input ':' (line 2))
re-run: mxr start WO1003 --site DEV (resumes; finished tasks are skipped) cancel: mxr cancel WO1003 --code TEST/GATE
WO1003 INPRG HOT commit 234be8b5f114
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 4
failure: TEST/GATE — lint fail: 1 of 3 scripts fail to compile: MXR_DEMO_BROKEN.py (no viable alternative at input ':' (line 2))
site DEV: failed
[G] lint scripts fail 1 of 3 scripts fail to compile: MXR_DEMO_BROKEN.py (no viable alternative at input ':' (line 2))
[G] dbc dbc pending
[G] migmgr config pending
[G] deploy deploy pending
[G] smoke smoke pending
[G] api api pending
[G] scripts-fn scripts-test pending
[ ] cron cron-test pending
[ ] esc escalation skip off in job plan
[ ] wf workflow skip off in job plan
[G] mif mif skip off in job plan
[G] ui ui pending
[ ] mobile ui-mobile skip off in job plan
[ ] perf perf skip off in job plan
[ ] drift drift pendingA cancelled work order carries one failure code and prints the recovery runbook for it, with the work order, the site and the paths filled in. Reverting is a person's decision, made with the facts in front of them.
$ mxr cancel WO1003 --code TEST/GATE --reason the change does not compile on the site's Jython
WO1003 CAN (TEST/GATE)
# TEST/GATE
Work order **WO1003** on **DEV**: a gated task of the job plan failed, so the work order stopped. Whatever the
deploy task applied before it is on the site; nothing after the failed task ran. The task's own output is in
`.mxr/runs/WO1003/run.json` → `sites.DEV.tasks[*].details`, and a Playwright task leaves its report, traces and
screenshots in `.mxr/runs/WO1003/playwright/<task id>/`.
## Read the failure first
| Task type | Where to look | Usual cause |
|---|---|---|
| `scripts` (lint) | the file and line in the message | a script that does not compile on this site's Jython; a script already on the site can fail too, which is a finding about the site, not about this change |
| `smoke` | `whoami.extended` through the agent, bundle pods | the key, the agent, or a bundle that is not Ready |
| `api`, `ui`, `ui-mobile` | `.mxr/runs/WO1003/playwright/<task id>/report.json`, `trace.zip`, screenshots | a journey that no longer matches the application (renamed field, new dialog), seed data on the wrong org/site, a browser login that expired |
| `scripts-test` | the call's status and body, or the record the launch point should have touched | an endpoint that answers 400/500, a launch point that did not fire on the test site |
| `cron-test`, `escalation`, `workflow`, `mif` | CRONTASKHISTORY, the seeded record, MAXINTMSGTRK | the instance is inactive, the condition excludes the seed, the external system is down |
| `config` (MigMgr) | the compare: `objects[]`, `conflicts[]` | the target changed after the package was built |
A site that must stay free of test data can switch the tests to virtual records: `test: { records: virtual }` makes
the api task validate a work order through MXR.AGENT (`mbo.validate`: defaults, field classes, required fields) and
discard it, and the journeys that would create records switch off. The gate still answers the same question.
## Then one of three
1. **Fix forward.** Change the repo (or the site, when the finding is about the site), commit, and `mxr plan` →
`mxr start <new wo> --site DEV`. The new work order carries the new commit.
2. **Resume.** When the cause was outside the change (a flaky journey, a bundle that was restarting), re-run
`mxr start WO1003 --site DEV`: tasks that passed are skipped, the failed one runs again. `TEST/UI-FLAKY`
is the code for a journey that passed on retry; the job plan records it as `flaky`, not `pass`.
3. **Revert what the deploy applied.** Before the push, mxR exported the live copy of every object it was about to
touch into `.mxr/runs/WO1003/export-before/DEV/hot/`. Copy it over `hot/`, commit, plan and start: a normal
HOT work order from a commit that equals the pre-push state. Nothing is deleted on the site.
Never switch a gate off to get past it. A gate that is wrong for a site is changed on the board (or in the job
plan file) by a commit, with the reason in the message.Built now
The next change plans itself.
With the broken script dropped and a real edit committed, mxr plan needs no --from: on a site without a ManageWorkspace the deployed sha is the last work order that completed there. The work order waits in WAPPR for the next mxr start.
$ mxr plan
deployed sha for DEV: 1e3e793a6929 from WO1001 (the last completed work order; this site has no ManageWorkspace)
WO1004 WAPPR HOT commit 336d1c45705c
job plan: standard-hot (10 tasks on, 8 gated)
changed paths: 3
tier HOT because:
HOT hot/scripts/MXR_DEMO_HELLO.py (goes in through REST)
HOT mxr.yaml (repo file: nothing to deploy)
HOT tekton/pm-nightly-drift.yaml (repo file: nothing to deploy)
next: mxr start WO1004 --site DEV$ ls -R .mxr/runs | head -60
.mxr/runs:
WO1001
WO1002
WO1003
WO1004
.mxr/runs/WO1001:
drift-DEV.diff
export-before
playwright
run.json
.mxr/runs/WO1001/export-before:
DEV
.mxr/runs/WO1001/export-before/DEV:
hot
.mxr/runs/WO1001/export-before/DEV/hot:
scripts
.mxr/runs/WO1001/export-before/DEV/hot/scripts:
MXR.AGENT.py
MXR.AGENT.yaml
.mxr/runs/WO1001/playwright:
api
ui
.mxr/runs/WO1001/playwright/api:
artifacts
report.json
.mxr/runs/WO1001/playwright/api/artifacts:
.mxr/runs/WO1001/playwright/ui:
artifacts
report.json
.mxr/runs/WO1001/playwright/ui/artifacts:
.mxr/runs/WO1002:
drift-DEV.diff
run.json
.mxr/runs/WO1003:
run.json
.mxr/runs/WO1004:
run.jsonBuilt now
The board: a job plan an admin composes in two minutes.
mxr ui serves one static page on localhost. Drag a task into a site, flip its gate, switch it off for DEV, save: the job plan file changes, and the next push raises a work order with it. The Work Order screen is the same record the CLI prints.


The palette has a Golden card, with app tags and a 10-minute default budget, so the screen check goes onto a site like any other task. The Work Order header shows the failure code on its own, with the full message one hover away. A failed test task names up to five failures in short form and points to the report for the rest. A promotion work order appears here too, with its source and its target.
Built now
Virtual records: a site that stays free of test data.
test: { records: virtual } asks Manage to validate a work order through its own business objects, database defaults, field classes and required fields, then discard it. Nothing reaches the database. The api task validates first, the journeys that would create records switch themselves off, and the read-only journeys still run. The gate then asks a narrower question, in-memory validation and read-only journeys, and its result names what was skipped; real records add save-time behaviour and the full journeys.
$ grep -n test: sites/DEV.yaml
8:test: { org: EAGLENA, site: BEDFORD, allowCreate: true, records: virtual }$ mxr doctor
· cluster checks: site has no cluster: block
✓ Manage apikey: whoami answers as MXINTADM
✓ MXR.AGENT: unchanged (version 0.3.1, from the repo)
✓ whoami.extended: Manage 9.2.237 · db2 · bundle all · MAXUPG V9200-175
✓ API key user MXINTADM: in ASSISTADMIN, ASSISTSTUDIOUSERS, ASSISTTECHUSERS, EVERYONE, ITEMMGR, LICENSEUSAGEVIEW, MAFCONFIGADMIN, MAXADMIN, MOBADMINAPPADMIN, MONITORINT, MONITOR_ADMIN, MONITOR_READ_ONLY, MONITOR_USERS, MREFINT, OPTIMIZERADMIN, OPTIMIZERUSERS, STOREROOMCLERK, TOOLMGR, USERMANAGEMENT
✓ Automation Scripts grants for the key's user: ATTRVAL, BMXVIEWMANAGEWHER, BMXVIEWMANAGEWHERRO, BOOKMARK, CFGACTN, CFGATTR, CFGCCOND, CFGIFACE, CFGOBJECT, CFGSCRIPT, CLEAR, CREATEKPI, DELETE, DELMCPTOOL, DUPLICATE, HIDEATTR, HIDEBUT, HIDEDEL, HIDEIMP, HIDELPATTR, IFACEESDIRRQ, IFACEESDIRRS, IFACEESIC, IFACEICDIRRQ, IFACEICDIRRS, IFACEPCESIC, IFACETYPE, IFACETYPEES, IFACETYPEIC, IFACETYPENO, IFACETYPEPC, IFACETYPEROLE, IFACEXIT, IFEXPORT, IFIMPORT, INTSCRIPT, LAUNCH, LPATTRVAL, MANMCPTOOL, NEXT, PREVIOUS, READ, RUNREPORTS, RUNSCRIPT, SAVE, SCRIPTCRON, SCRIPTEP, SCRIPTERR, SCRIPTFORM, SCRIPTIN, SCRIPTNOOBJ, SCRIPTOTHER, SCRIPTOUT, SCRIPTREST, SCRIPTWHERE, SEARCHBOOK, SEARCHMORE, SEARCHSQRY, SEARCHTIPS, SEARCHVMQR, SEARCHWHER recorded for ASSISTADMIN, ASSISTSTUDIOUSERS, ASSISTTECHUSERS, EVERYONE, ITEMMGR, LICENSEUSAGEVIEW, MAFCONFIGADMIN, MAXADMIN, MOBADMINAPPADMIN, MONITORINT, MONITOR_ADMIN, MONITOR_READ_ONLY, MONITOR_USERS, MREFINT, OPTIMIZERADMIN, OPTIMIZERUSERS, STOREROOMCLERK, TOOLMGR, USERMANAGEMENT, and the agent install worked, so the key has what it needs
✓ test site BEDFORD: a virtual work order validates (would be 1293, status WAPPR); nothing saved
· bucket: no bucket in mxr.yaml (needed for REBUILD)
· admin image pull: no cluster.adminImage in the site file (needed for Java builds)
all checks passed$ mxr inspect --jobplan virtual-check
WO1005 INSPECT → DEV (virtual-check)
▶ smoke (smoke, gate, budget 5m)
✓ smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
▶ api (api, gate, budget 10m)
✓ api pass virtual work order validates on BEDFORD (would be 1294, nothing saved); 1 passed, 1 skipped
▶ scripts-fn (scripts-test, gate, budget 5m)
✓ scripts-fn pass 1 script test(s) passed
▶ ui (ui, gate, budget 15m)
✓ ui pass 1 passed
✓ WO1005 COMP
WO1005 COMP INSPECT commit 50332c78559f
job plan: virtual-check (4 tasks on, 4 gated)
changed paths: 0
site DEV: done
[G] smoke smoke pass whoami.extended ok (MXINTADM on Manage 9.2.237 (db2, bundle all))
[G] api api pass virtual work order validates on BEDFORD (would be 1294, nothing saved); 1 passed, 1 skipped
[G] scripts-fn scripts-test pass 1 script test(s) passed
[G] ui ui pass 1 passed$ curl -s -H 'apikey: <APIKEY>' 'https://manage.example.com/maximo/api/os/mxapiwodetail?lean=1&oslc.select=wonum,status,description&oslc.where=description%3D%22mxr%20WO%25virtual%25%22' | python3 -c "import json,sys; d=json.load(sys.stdin); print('work orders with a virtual description on the site:', len(d.get('member',[])))"
work orders with a virtual description on the site: 0Built now
Golden screens and journeys: every application still opens and still works.
mxr export --golden asks Manage which classic applications the key's user may read and opens each one once, inside the shell frame with one UI session: on this lab 286 screens in about eight minutes, 1.6 seconds a screen after the first, with the title, control types, toolbar actions, tabs and Select Action entries of each recorded in tests/golden/DEV.json. With --deep it also records each screen's journeys: More Search Fields opened and cancelled, the saved queries opened, the list's first record opened and every tab and sub-tab walked. The golden task turns the baseline into Playwright tests, one per screen and one per journey, and runs the journeys on the screens a push touched. On this lab the whole baseline is 287 screens and 894 generated tests (235 search dialogs, 258 query menus, 114 records with 783 tabs and sub-tabs), and a weekly job plan replays all of them in about 50 minutes. All read-only, so they run on PROD and with virtual records.
$ mxr export --golden --site DEV --apps WOTRACK,ASSET,PO,ITEM,PERSON,PLUSAWO,SCCONFIG
capturing 7 screen(s) on DEV → tests/golden/DEV.json
1/7 WOTRACK ok 66 controls 8121 ms Work Order Tracking
2/7 ASSET ok 72 controls 3255 ms Equipment
3/7 PO ok 58 controls 1781 ms Purchase Orders
4/7 ITEM ok 57 controls 1591 ms Part Number Master
5/7 PERSON ok 62 controls 1886 ms People
6/7 PLUSAWO ok 60 controls 1676 ms Work Order Tracking (CM)
7/7 SCCONFIG ok 78 controls 1708 ms Layout and Configuration
captured 7 screens on DEV: ok 7 → ~/maximo-repo/tests/golden/DEV.json
7 screen(s) in tests/golden/DEV.json: 7 ok; 2860 ms a screen on average
commit it: git add tests/golden/DEV.json$ MXR_GOLDEN_APPS=WOTRACK,PERSON MXR_GOLDEN_DEEP=1 npx playwright test -c tests/ui/playwright.config.ts --project golden --list
Listing tests:
../golden/golden.spec.ts:101:11 › Golden screens on DEV @golden › WOTRACK Work Order Tracking opens @golden:WOTRACK
../golden/golden.spec.ts:114:13 › Golden screens on DEV @golden › WOTRACK Work Order Tracking: More Search Fields opens and cancels @golden:WOTRACK @golden-deep
../golden/golden.spec.ts:123:13 › Golden screens on DEV @golden › WOTRACK Work Order Tracking: Available Queries opens @golden:WOTRACK @golden-deep
../golden/golden.spec.ts:132:13 › Golden screens on DEV @golden › WOTRACK Work Order Tracking: a record opens and every tab and sub-tab works @golden:WOTRACK @golden-deep
../golden/golden.spec.ts:101:11 › Golden screens on DEV @golden › PERSON People opens @golden:PERSON
../golden/golden.spec.ts:114:13 › Golden screens on DEV @golden › PERSON People: More Search Fields opens and cancels @golden:PERSON @golden-deep
../golden/golden.spec.ts:123:13 › Golden screens on DEV @golden › PERSON People: Available Queries opens @golden:PERSON @golden-deep
../golden/golden.spec.ts:132:13 › Golden screens on DEV @golden › PERSON People: a record opens and every tab and sub-tab works @golden:PERSON @golden-deep
Total: 8 tests in 1 file$ mxr inspect --site DEV --jobplan golden-deep
WO1009 INSPECT → DEV (golden-deep)
▶ golden (golden, gate, budget 30m)
✓ golden pass 10 screen(s), journeys on every one. 34 passed
✓ WO1009 COMP
WO1009 COMP INSPECT commit 4a44def30a36
job plan: golden-deep (1 tasks on, 1 gated)
changed paths: 0
site DEV: done
[G] golden golden pass 10 screen(s), journeys on every one. 34 passed$ MXR_GOLDEN_FILE=tests/golden/DEV-broken.json MXR_GOLDEN_APPS=PERSON MXR_GOLDEN_DEEP=1 npx playwright test -c tests/ui/playwright.config.ts --project golden --reporter=line
Running 4 tests using 1 worker
[1/4] golden.spec.ts:101:11 › Golden screens on DEV @golden › PERSON People opens @golden:PERSON
1) golden.spec.ts:101:11 › Golden screens on DEV @golden › PERSON People opens @golden:PERSON
Error: Select Action entries gone: Approve Payroll
[2/4] golden.spec.ts:114:13 › Golden screens on DEV @golden › PERSON People: More Search Fields opens and cancels @golden:PERSON @golden-deep
2) golden.spec.ts:114:13 › Golden screens on DEV @golden › PERSON People: More Search Fields opens and cancels @golden:PERSON @golden-deep
Error: the search dialog reads "More Search Fields" (the baseline recorded "Advanced Person Search")
[3/4] golden.spec.ts:123:13 › Golden screens on DEV @golden › PERSON People: Available Queries opens @golden:PERSON @golden-deep
[4/4] golden.spec.ts:132:13 › Golden screens on DEV @golden › PERSON People: a record opens and every tab and sub-tab works @golden:PERSON @golden-deep
3) golden.spec.ts:132:13 › Golden screens on DEV @golden › PERSON People: a record opens and every tab and sub-tab works @golden:PERSON @golden-deep
Error: tabs gone: Payroll
3 failed
golden.spec.ts:101:11 › Golden screens on DEV @golden › PERSON People opens @golden:PERSON
golden.spec.ts:114:13 › Golden screens on DEV @golden › PERSON People: More Search Fields opens and cancels @golden:PERSON @golden-deep
golden.spec.ts:132:13 › Golden screens on DEV @golden › PERSON People: a record opens and every tab and sub-tab works @golden:PERSON @golden-deep
1 passed (1.4m)$ mxr inspect --site DEV --jobplan golden-smoke
WO1007 INSPECT → DEV (golden-smoke)
▶ golden (golden, gate, budget 10m)
✓ golden pass 11 screen(s); not in the baseline (re-capture to add): PLUSAPOS. 11 passed
✓ WO1007 COMP
WO1007 COMP INSPECT commit ffa84008066e
job plan: golden-smoke (1 tasks on, 1 gated)
changed paths: 0
site DEV: done
[G] golden golden pass 11 screen(s); not in the baseline (re-capture to add): PLUSAPOS. 11 passed$ sed -n '1,32p' tests/golden/DEV.json
{
"site": "DEV",
"capturedAt": "2026-10-31T17:57:24.099Z",
"user": "<operator>",
"apps": {
"WOTRACK": {
"title": "Work Order Tracking",
"status": "ok",
"ctypes": [
"qstextbox",
"toolbarbutton",
"navsearch",
"label",
"toggleimage",
"sortlabel",
"textbox",
"image",
"ttdialog"
],
"controls": 66,
"toolbar": [
{
"key": "toolactions_INSERT-tbb",
"text": "New Work Order",
"region": "toolbar"
},
{
"key": "toolactions_SAVE-tbb",
"text": "Save Work Order",
"region": "toolbar"
},
{Every UI test shares one browser page and one Manage session for the run, and opens applications and records inside the shell frame. REA read the traces of the same journeys before and after, on this lab with real test records on BEDFORD:
| Journey | One page per test | One shared session | What changed |
|---|---|---|---|
| Work Order Tracking: create, approve, cancel | 28.8 s | 14.2 s | two app opens inside the frame instead of two shell reloads |
| Assets: look up a seeded asset | 21.6 s | 14.0 s | includes the run's one sign-in |
| The whole run | 73.6 s, 4 shell loads, 902 requests | 61.2 s, 1 shell load, 749 requests | REA on the traces |
REA is also where the method came from: open inside the shell frame with the current UI session, read the loadapp answer before waiting, wait for the title bar or the first control rather than for network idle. On the journey run the loadapp answer took 183 ms at the median across 37 opens, with one sign-in for all 34 tests.
A weekly run of 894 tests is built to finish. One browser page serves up to 150 tests (MXR_PAGE_TESTS) and is then replaced, and it is replaced at once after a crash, with the crashed page signed out so its Manage session slot is freed. A golden test that meets a page crash gets one retry on a fresh page and counts as flaky, not failed. Manage allows ten UI sessions per HTTP session: when that cap is reached, the first test names it, the remaining tests are skipped and the task reports AUTH/INFRA with mxe.webclient.maxUISessionsPerHttpSession as the property to raise. A site that stops answering ends the run once, as AUTH/INFRA, in one line.
$ node /home/user/mxr/scripts/rea-report.mjs .mxr/runs/WO1007/playwright/golden
11 trace(s), 1137 requests. REA ev_89612578d880001a41d4f019d5cd1b24739541948b72bf3403def51dee7b38b0 (1401 records)
kind count p50 ms p95 ms max ms failed
static asset 1007 1 193 1313 0
other 95 83 110 1623 0
app open (loadapp) 14 235 821 821 0
REST (api/oslc) 11 92 4535 4535 0
classic async event 6 92 138 138 0
login / session 2 87 87 87 0
shell long poll 2 1125 1125 1125 2
app opens: 14, p50 235 ms, p95 821 ms (the loadapp answer; the screen draws after it)
STARTCNTR 821 ms HTTP 200
ASSET 421 ms HTTP 200
WOTRACK 293 ms HTTP 200
PO 256 ms HTTP 200
DOMAINADM 255 ms HTTP 200
PLUSAWO 247 ms HTTP 200
SCCONFIG 235 ms HTTP 200
ITEM 226 ms HTTP 200
PERSON 218 ms HTTP 200
CRONTASK 213 ms HTTP 200Built now
Inside Maximo: what the run left behind.
Two Automation Scripts: the agent that mxR installs on every site, and the script the work order deployed. The tests that create records cancel or close them on the test site.



Built now
Keep your runner. Add the step that knows Maximo.
Azure DevOps Pipelines, GitHub Actions, GitLab CI and Jenkins trigger, schedule, hold approvals and keep logs for any software. The rows below are what a Maximo release needs on top of that, and where each approach gets it.
| What a Maximo release needs | mxR | Azure DevOps Pipelines · GitHub Actions · GitLab CI · Jenkins | Migration Manager, scripts and a person |
|---|---|---|---|
| The unit of change | A work order with Maximo's own statuses, raised from the diff (Git, or the versions mxR saves) and kept in your repo folder and run record, never as a record in Maximo. Promotion is the same work order on the next site. | A pipeline run, a stage or a release. Giving it Maximo meaning (what was deployed where, with which commit) is a convention you define and maintain. | A change package and a ticket, matched up by a person. |
| Which kind of deploy | The path rule reads what changed (git diff, or the saved versions when there is no Git) and decides HOT, RESTART or REBUILD. Three folders, one rule, printed with every plan. | Conditions you write in the pipeline YAML, per artefact folder and per environment, and keep in step with the site files. | An administrator decides case by case. |
| Knowing Maximo artefacts | Scripts with launch points and variables, 22 record types through their object structures, presentations, Migration Manager packages and MAF apps through the agent. Each has preview, apply and export, in one hard-coded order. | Generic steps: a script, a REST call, a container. The Maximo calls per artefact type, their order and their activation steps are yours to write. | The application and the package import, in the order the person follows. |
| Preview before apply | Every item is compared with the site (create, update, unchanged) and a tripped guard blocks the push. A dry run writes nothing, including its own state. | What your step does; a dry run is a mode you build into each step. | The package preview for Migration Manager; the Save button for the rest. |
| Tests that know Maximo | 18 task types with a default test per artefact: Jython compiled on the site, script endpoints called, launch points fired, cron run now, Playwright on /maximo/api and on classic through the suite login, every touched screen opened against a golden baseline captured on your own site, with its search, saved queries and every tab of a record, drift as export and diff. Gates and budgets per task. A site that must stay free of test data switches to virtual records: Manage validates a work order through its own business objects and nothing is saved. | Your test framework, plus the Maximo fixtures: suite login, idle waits on classic, seeding on the test org and site, cleanup by cancel rather than delete. | A person checks the application after the deploy. |
| Deployed state | Annotations on the ManageWorkspace: the version (a git sha, or the saved version id without Git), archive sha, backup reference, deployed at. A site without a CR uses its last completed work order. Nothing to drift. | The run history of the tool. The Maximo side carries no record of which commit it runs. | A spreadsheet or the package history. |
| When something fails | One failure code on the work order, run.json with every applied, skipped and failed item, the pre-push export of every touched object, the same command to resume, and the runbook printed with the paths filled in. | Your deployment scripts and recovery conventions: what applied before the failure, and how to get back, is yours to reconstruct. | The person on the deploy, and the backup. |
| A rebuild | Maven against the admin-image jars, DBC validation and the dbversion bump, a deterministic zip with its SHA-256, an in-cluster HEAD before the CR patch, Ready as pods + whoami + maxinst, all behind a backup reference, a window and an approval. | A build container and an operator patch you script and keep current with each Manage release. | The ManageWorkspace edited by hand in a maintenance window. |
| Secrets and footprint on Maximo | One API key from an environment variable and one Jython script (MXR.AGENT) on the site. Manage's own security decides what the key may do; the agent reads only hot artefact tables and writes only to them. | Variable groups or secrets, service connections, agents or runners with network access to Manage; the Maximo side is whatever your scripts need. | An operator account under local access policy. |
| Who composes the pipeline | A Maximo administrator, on the board: drag a task into a site, flip its gate, save. The result is a YAML commit. | A pipeline engineer in YAML, with the Maximo knowledge supplied by the team. | A written procedure. |
| Add-ons and industry solutions | The same deployers and tests, because an industry solution adds applications, objects and scripts rather than a new way to deploy them. When an add-on field refuses the records the tests create, doctor names the attribute, its default and its field class before any gate (found on Maximo for Aviation). | Whatever your scripts handle; an add-on's own validations show up as errors in the stage log. | The person knows the add-on. |
| Where it runs | Tekton on the cluster today. The CLI runs in any runner with Node 22 and a route to Manage: an Azure DevOps stage, a GitHub Actions job, a GitLab CI job or a Jenkins stage calls mxr start. | The runner itself. | A workstation. |
Keep your runner. Azure DevOps, GitHub Actions, GitLab CI and Jenkins keep the triggers, the approval screens and the audit trail you already use; mxr start is the step inside them that knows Maximo. On the cluster, mxr pipeline render emits the same run as a Tekton PipelineRun.
Built now
Your Maximo, your runner.
Every call goes through /maximo/api with an API key and the agent is a script Manage runs itself, so mxR follows Manage across releases. The run on this page was captured on Manage 9.2.237.
- Proven and built for — Proven on Manage 9.2.232 and 9.2.237, built for every release IBM supports. Hot changes are proven end to end on Manage 9.2.232, and on 9.2.237 with the Aviation and Service Provider add-ons. Every call goes through /maximo/api with an API key, and the agent is a Jython script Manage runs itself, so the same commands and job plans are built for every Manage release in IBM's support window. Release by release results are in docs/support-matrix.md in the mxR repo.
- Add-ons and industry solutions — Aviation, Oil and Gas, Utilities, Nuclear, Transportation, Health, Civil Infrastructure, Scheduler, Mobile, Spatial. An industry solution adds applications, objects and scripts; it does not add a new way to deploy them. A script, a presentation, a Migration Manager package or a MAF app from an industry solution goes in through the same deployer, and the UI journeys take the app tags of the applications you run. This run was captured on a Maximo Aviation lab (Manage 9.2.237 with the Aviation and Service Provider add-ons), where doctor found the one add-on field that refused every work order and named it before a test ran.
- Your runner — Tekton, Azure DevOps, GitHub Actions, GitLab CI, Jenkins. mxR is a Node CLI (22 LTS; 20 still runs). Run it from your laptop, from a Tekton Task on the cluster, or from the stage of the pipeline tool your company already runs. Your repo folder stays the source of truth wherever it is called from, with Git or without.
Built now
Three tiers, decided by the paths that changed.
The paths that changed between the deployed version and now are the whole classification: git diff <deployed-sha>..HEAD --name-only with Git, the same list from mxr history without it. The highest tier wins, and the plan prints the reason for every path.
| Changed | Tier | What happens | Needs |
|---|---|---|---|
| hot/**, tests/**, jobplans/** | HOT | Preview every item, apply in the hard-coded order through REST and MXR.AGENT, record every item in run.json, run the job plan. Minutes. | The API key |
| Any other sites/*.yaml key: the cr: overlay (server bundles, JVM options, JMS, imported certificates, persistent volumes) | RESTART | Merge the overlay into the ManageWorkspace spec and wait for Ready within the restart timeout. | Cluster access |
| archive/**, or the site-file keys components, languages, buildTag, archives | REBUILD | Build <name>-<gitsha>.zip (Maven against the admin-image jars, DBC checks, dbversion bump), upload it, HEAD it from inside the cluster, patch the ManageWorkspace, wait for pods Ready, whoami answering and maxinst exited 0. Behind a backup gate and a window; recovered by a person from the runbook. | Cluster, bucket, admin image |
A site file is read at both versions, so only the keys that moved count: a replica change is a RESTART, a buildTag change is a REBUILD. Tests and job plans deploy nothing; the job plan picks them up.
Built now
One work order, worked on one site at a time.
Site gates live in mxr.yaml: DEV has none, TEST wants a backup reference, PROD wants a backup, a window and an explicit approval by a listed approver. The backup gate applies to a RESTART or a REBUILD; a HOT change passes it. The window and approval gates apply to every tier. A backup reference is the evidence the approver supplies; mxR records it with the approval and prints it in every runbook. Task gates in the job plan (lint, dbc, deploy, smoke, api, scripts, ui) block on every site, DEV included.
- Statuses — WAPPR → APPR → INPRG → COMP → CLOSE. CAN from any open status. COMP → INPRG is promotion to the next site. Every change is recorded in run.json with who made it and when.
- Approvals — Recorded, and signed when you want. mxr approve records the approver (MXR_ACTOR, git user.name or your login name); with MXR_APPROVAL_SECRET set the marker is signed and start verifies it. The signature proves the approver held the secret; the runner's approval screen is where a login is bound to a PROD approval. A site without an approval gate self-approves and says so.
- Resume — State before action. run.json is written before every task and every hot item. mxr start on a work order in progress skips finished tasks and continues; --dry-run leaves the record exactly as it was.
| Exit code | Meaning |
|---|---|
| 0 | ok |
| 1 | a task or deploy failed |
| 2 | a gate blocked the run; the work order holds INPRG |
| 3 | infrastructure or authentication |
| 4 | bad input |
Built now
Promote from one site to another
Point mxR at a source site and a target site. It lists exactly what differs, your Maximo administrator approves that list, and mxr start promotes it through the same work order steps: WAPPR, APPR, INPRG, COMP. A promotion needs the API keys of the two sites and nothing else.
DEV to UAT
read -rs MXR_DEV_APIKEY && read -rs MXR_UAT_APIKEY && export MXR_DEV_APIKEY MXR_UAT_APIKEY
mxr promote plan --from DEV --to UAT --dry-run # print the list
mxr promote plan --from DEV --to UAT # raise WO1042 in WAPPR with the list attached
mxr approve WO1042 # who, when and the list hash
mxr start WO1042 # read DEV again, promote to UAT, run the job plan, COMP
mxr inspect --promotion WO1042 # the approved list, the approval, what was applied
mxr ui shows the list before anyone approves it. After mxr start the same screen shows what was applied, and the work order is COMP.

UAT to PROD
read -rs MXR_PROD_APIKEY && export MXR_PROD_APIKEY
mxr inspect --diff --from UAT --to PROD # what differs right now
mxr promote plan --from UAT --to PROD # raise WO1043 in WAPPR
mxr approve WO1043 # PROD's approval gate
mxr start WO1043 --window "Sat 22:00" # PROD's window gate, then the promotion| Group | What it holds | What mxr start does |
|---|---|---|
| New | On the source, missing on the target | Promotes it |
| Changed | On both sites, and the source has something to write: an attribute, or a child row to add (+) or change (~) | Promotes it |
| Only on the target | On the target, missing on the source; for child rows, NAME#child with the rows the target keeps | Keeps it as it is |
| Excluded | Matches promote.exclude in mxr.yaml or the built-in list | Holds it back and shows the pattern and the reason |
- The list — Read with the export code. mxr promote plan exports both sites with the code mxr export --all uses, into .mxr/promote/, through the export: filters in the source site file. It compares scripts, presentations, Migration Manager packages, MAF apps and the 22 hot record types, item by item. A record is compared on its content: rowstamp, changedate, changeby and the row's own id (MAXDOMAINID, CONDITIONID) are set by each site, so they are left aside.
- Child rows — Domain values, grants, details. Each record comes with its child rows: domain values, object structure details, endpoint properties, external system interfaces, application grants, cron parameters, work periods and report parameters. MXR.AGENT reads them table by table (SYNONYMDOMAIN, APPLICATIONAUTH) and mxR matches each row by its own key. The list shows what the source adds and changes, for example synonymdomain +NEW ~DONE. Every write merges, so rows the target has on its own stay as they are and show as NAME#child.
- Tiers and hashes — The path rule decides, as for a Git diff. Each item has the path mxr export gives it, so the path rule gives it the tier a Git-raised work order would: hot/** is HOT. Each item carries a sha256 of its content, the work order carries a list hash over them all, and an approved copy of the items sits in .mxr/runs/<WO>/promote/source/.
- Approval — One approval, one list. mxr approve records who approved, when, and the list hash, signed with MXR_APPROVAL_SECRET when you set it. mxr promote plan --refresh WO1042 makes the list again; when the hash changes, an approved work order goes back to WAPPR and says why. The gates in mxr.yaml apply as they do to every work order: PROD wants an approver and a window.
- Start — Checked, then promoted. mxr start reads the source again and compares every approved item with its hash. A changed item stops the run with exit code 2 and names the item, before anything reaches the target. The approved copy then goes in through the same hot deploy, in the same order, and the target's job plan runs with the same gates and failure codes. A failed task resumes where it stopped.
- Deployed state — On the target's ManageWorkspace. COMP writes mxr.io/promoted-from, mxr.io/promotion-wo, mxr.io/promotion-items and mxr.io/promotion-hash on the target. mxr.io/git-sha stays with Git-raised work orders, so mxr plan keeps diffing from the last commit the site runs.
- The board — Source, target and four groups. mxr ui shows a promotion work order with its source and target, each group with its items, tiers and hashes, and the result of every item on the target.
These stay with each site. mxR lists them under Excluded and holds them back (src/promote/exclude.ts):
| Pattern | Why it stays with the site |
|---|---|
| hot/endpoints/** | Endpoint URLs and handler properties point at each site's own systems |
| hot/extsystems/** | External systems carry each site's endpoints and enabled flags |
| hot/properties/** | System properties hold site values: hosts, folders, mail servers, keys |
| hot/jmsconsumers/** | JMS consumer cron instances run on the schedule each site sets |
| hot/startcenters/** | Start centre templates are keyed by SCTEMPLATEID, a number each site gives itself |
| hot/translations/** | Translations are keyed by OWNERID, a number each site gives itself |
| hot/securitygroups/MAXADMIN.yaml | MAXADMIN stays as each site has it |
| hot/securitygroups/**#groupuser | Group members are users, and each site has its own; the grants promote |
| hot/persongroups/**#persongroupteam | Person group members are people records, and each site has its own |
| hot/crontasks/**#crontaskinstance | Cron task instances carry the schedule and active flag each site sets |
Your own exclusions in mxr.yaml
promote:
exclude:
- hot/crontasks/CUSTSYNC.yaml # a cron task each site runs its own way
- hot/conditions/CUST_UAT_* # the path rule's syntax: ** for any folders, * inside one name
- "hot/commtemplates/**#commtmpltsendto" # #child holds back one child set and promotes the rest
# defaults: false # use your own list on its ownA Migration Manager package goes in from your repo's hot/migmgr/<NAME>.zip, and its sha256 joins the list hash. For a package your administrator deploys in Migration Manager, the list names the step.
Built now
No Git needed: mxR keeps the history
Every time mxr runs it looks at the files it manages and records a new version when they differ from the latest. There is always an answer to what changed, when and by whom, and nothing here needs Git. When mxr.yaml is tracked in a Git repo that has a commit, mxR uses Git instead and keeps nothing of its own.
Read it, compare, go back, move to Git
mxr history list # v00001-27eccbf96cc4 2026-11-04 06:39:50 ana run +8 ~0 -0
mxr history diff -u # what changed in the latest version, line by line
mxr history diff v3 v5 # or between any two
mxr history restore v3 --apply # the folder as it was at v3; the folder as it is now is recorded first
mxr history export-git --to ../repo # one commit per version, with who and when
Go back whenever you like: the restore is a dry run until you add --apply, and the folder as it is now is recorded first. When you move to Git, the history becomes commits.

| What Git did | With Git | Without Git |
|---|---|---|
| Names the version a work order deploys | The commit | A version id, v00007-a1b2c3d4e5f6: its number, then a fingerprint of the files |
| Says which paths changed (the path rule's input) | git diff --name-only | The two versions compared |
| Gives an old copy of a file | git show | The bytes saved in .mxr/history/objects |
| Reports drift after an export | git status | The folder recorded just before and just after the export |
- What is recorded — Your files, once each. Everything under the repo folder except .git, .mxr, node_modules, build and test output, *.log and anything listed under versions.ignore in mxr.yaml. A file is stored once however many versions hold it; a file over 50 MB is fingerprinted and not copied.
- The first plan — Measured from the export. mxr export records the version each site was exported at. The first mxr plan for a site with no deployed version measures from it, so it needs no --from. After that the deployed version is read from the ManageWorkspace annotation, or from the last work order that completed there.
- What a work order deploys — The version it recorded. mxr start writes that version's hot/ to .mxr/runs/<WO>/version/ and deploys from there. An edit made after the plan, or the same work order started on the next site days later, deploys the files that were approved.
- Moving to Git — Nothing is lost. mxr history export-git --to <folder> builds a Git repo with one commit per version, using the author and time mxR recorded, and writes git-map.json so a deployed version id from before the move still resolves. Once mxr.yaml is tracked in a repo with a commit, mxR uses Git.
mxr.yaml
versions:
mode: auto # auto picks Git when mxr.yaml is tracked in a repo with a commit; or git, or folder
ignore: [backups, '*.bak']Pull-request drift (mxr drift-pr), mxr plan --from auto, mxr start --remote and the CI adapters work from a Git repo. A pipeline that checks out a fresh copy on every run keeps its history in Git, or keeps .mxr/history on storage that persists.
Built now
Nothing runs that is not a task.
jobplans/standard-hot.yaml is the default plan for a HOT change; full-rebuild, drift and weekly-ui ship with it. Each task has on, gate, test, a budget and app tags.
| Task | Type | Defaults | What it does |
|---|---|---|---|
| lint | scripts | on · gate | Jython compiled on the site itself through MXR.AGENT script.compile, the interpreter that will run it; JavaScript parsed with GraalJS or node |
| dbc | dbc | on · gate | well-formed XML, one <sql target> per database platform, dbversion bumped |
| migmgr | config | on · gate | Migration Manager package compare and per-item preview, read-only; conflicts block |
| deploy | deploy | on · gate | the tier deploy |
| smoke | smoke | on · gate · 5m | whoami.extended through the agent, bundle pods Ready, maxinst exit after a rebuild |
| api | api | on · gate · 10m | Playwright request tests on the test org and site; records are cancelled or closed |
| scripts-fn | scripts-test | on · gate · 5m | POST /api/script/{name} with the sidecar's tests: block, and a launch-point fire with an assertion |
| cron | cron-test | on · 5m | MXR.AGENT cron.runNow, then CRONTASKHISTORY |
| esc, wf | escalation, workflow | off | seed a record, run the escalation or initiate the workflow, assert ESCSTATUS or WFINSTANCE |
| mif | mif | on · gate · 8m | inbound POST /meaweb/es, MAXINTMSGTRK and MAXINTERRORMSG, outbound through an in-cluster WireMock; runs when tests/mif holds cases and the site names its external system |
| ui | ui | on · gate · 15m | Playwright journeys in Manage classic, filtered by app tags such as @WOTRACK |
| golden | golden | on · gate · 10m | every screen the push touched still opens with its title, control types, toolbar, tabs and Select Action entries against tests/golden/<SITE>.json, and on those screens More Search Fields, the saved queries and a record with every tab still work; read-only, on PROD and in virtual mode too |
| mobile, perf, drift | ui-mobile, perf, drift | off · off · on | one Technician journey, k6 report-only, export and diff report-only |
A budget is a wall-clock limit: a task that overruns fails. A gated failure stops the plan with exit 2 and the work order holds INPRG; a report-only failure is recorded and the plan continues.
Built now
Every hot artefact is a file; the order is one array.
hot/scripts, hot/presentations, hot/migmgr, hot/domains and 22 typed folders in all. Each deployer previews with no side effects, applies, and exports.
- live-refresh properties
- domains → object structures → endpoints → external systems → channels → integration controls → queues → JMS sequential consumers
- conditions → scripts with their launch points
- presentations (app XML, lookups, library, menus, sigoptions) → security groups → start centres and Update Start Center
- roles, actions, communication templates, calendars, person groups → workflows (new revision, activate) → escalations → cron tasks (activate, reload)
- report designs and a report cache refresh → translations → MigMgr packages
- MAF app definitions
- Preview first — Compare before any push. Every item is compared with the site: create, update or unchanged. A tripped guard blocks the whole push, so a domain value or attribute that may still be referenced stays where it is.
- Object structure or agent — Two ways in. A typed record goes through its object structure when the site has it, and through MXR.AGENT mbo.upsert when the site answers BMXAA4216E. You override the structure name per site.
- Hot revert — Pre-push export. Before the push, the live copy of every touched object is exported into the run folder. mxr cancel shows how to turn it into a revert work order.
Built now
A rebuild behind a window and a backup gate.
The archive mirrors the customization archive exactly: applications/, tools/maximo/, additional-server-files/. Java under archive/<name>/java builds with Maven against the admin-image jars, extracted once.
- DBC — Checked before any patch. Well-formed XML, one <sql target> for db2, oracle and sqlserver in every freeform, and a bumped <dbversion> in a_customer.xml when any .dbc changed. updatedb keys on dbversion, so the build enforces it.
- Archive — <name>-<gitsha>.zip. A deterministic zip with its SHA-256, uploaded to the bucket. The CR gets the URL only after a HEAD from a short Job in the Manage namespace gets an answer, so an archive the cluster cannot reach fails before the patch.
- Ready — Three things, not one. The Ready condition on the CR, every server-bundle pod ready, and whoami.extended answering; after a rebuild, maxinst exited 0 with UpdateDB/Maxinst completed.
A RESTART merges the site file cr: overlay (server bundles, JVM options, JMS, imported certificates, persistent volumes) into the CR spec and waits with the restart timeout. Recovery from a bad rebuild is a runbook, run by a person from the backup reference recorded at approval.
Built now
POST /maximo/api/script/MXR.AGENT?action=…
Installed and updated through the Automation Scripts object structure by doctor and start, versioned like any other hot artefact (0.3.1 on this run). Every call answers one JSON envelope with ok, data, the error type and every attempt it made.
| Actions | What they do |
|---|---|
| whoami.extended | the calling user and groups, bundle, database platform, MAXUPG build, Java version, admin mode; the CLI adds /api/systeminfo |
| script.compile | compile a Jython source on the site's own interpreter and return the error with its line, so lint fails the way Manage would |
| echo, describe | request wiring, and the real method list of a server class, one line each to extend the agent |
| mbo.query, mbo.upsert | read any table with no object structure (CRONTASKHISTORY, MAXINTMSGTRK, WFINSTANCE, DMPACKAGE) and write typed records with their child sets, hot artefact tables only |
| migmgr.list, migmgr.preview, migmgr.deploy | list packages, compare a change package before any push, deploy it through DMService |
| presentation.export, presentation.import | read and write an application presentation and refresh the UI cache |
| cron.runNow, cron.reload | run a cron instance now, flag instances for reload |
| workflow.initiate, workflow.complete, workflow.activate | route a record, answer its assignments, activate a new revision |
| escalation.activate, startcenter.update, report.refreshCache, report.import, maf.publish, property.refresh | the activation step each artefact type needs after its write |
Reads for tables without an object structure go through mbo.query; writes go through the typed actions and reach hot artefact tables only. It leaves MAXUSER, APIKEYTOKEN, PASSWORDHISTORY, LOGINTRACKING, MAXSESSION, USERPASSWORD, the MAXADMIN group and every security property alone, whoever calls it, and writes one audit line per call to the Manage log. It runs as the caller, so the key's security group is the first layer and this allowlist the second. The agent is plain Jython 2 with no standard-library imports, so it runs on the interpreter Manage ships. Build time only, with no language model anywhere in the product.
Built now
A test for every artefact type, from a Jython script to a MAF app.
Every artefact type has a built-in default test; test: true turns it on. Gates are lint, dbc, migmgr, smoke, api, scripts, mif, ui and golden; drift and perf are report-only.
- UI — Manage classic, one page model. getByRole and getByLabel first, hand-written control IDs from a 9.2 capture second. Log in once through the suite login form (the user named by the site's env vars) and reuse the session; one re-login on 401. Classic is driven inside the Manage shell's frame, and waitForMaximoIdle counts outstanding XHRs and watches the busy indicator.
- Journeys — All of them, every time. Work Order Tracking create and approve, Asset lookup, Purchase Order create, one Technician journey at a phone viewport. Data is seeded through the API on the test org and site and cancelled afterwards. One retry marks a test flaky.
- MIF — Inbound and outbound. POST /meaweb/es/{extsys}/{service} with the case payload, then MAXINTMSGTRK and MAXINTERRORMSG through the agent. Outbound points at an in-cluster WireMock and reads its request log.
- Golden screens — One generated test per application. mxr export --golden opens every application the key's user may read and records its title, control types, toolbar actions and tabs into tests/golden/<SITE>.json. The golden task then opens the screens a push touched (or all of them) and compares. With --deep the baseline also holds each screen's journeys, More Search Fields, the saved queries and the first record with every tab and sub-tab, and the screens a push touched replay them. A baseline per site, because an add-on renames applications: Maximo for Aviation calls Assets Equipment and Item Master Part Number Master. Read-only, about two seconds a screen, one Manage session for the whole run: on the Aviation lab, 286 screens captured at 1.6 seconds each and the whole baseline checked in 8 minutes 15 seconds.
- Real records — test: { records: real }. The default. Tests create their records on the test org and site through the object structures and cancel or close them afterwards. The Maximo screenshots in step 15 show what that leaves behind.
- Virtual records — test: { records: virtual }. For a site that must stay free of test data. MXR.AGENT adds a work order, sets its values, runs Manage's own validation (database defaults, field classes, required fields) and discards it, so nothing reaches the database. The journeys that would create records switch off; the asset lookup opens an asset the site already has. The result names what was skipped: virtual mode checks validation and the read-only journeys, real mode adds save-time behaviour. Step 14 shows the run.
| Type | Mechanism | Verdict |
|---|---|---|
| scripts | compile on the site through the agent, then POST /api/script/{name} with the sidecar's tests and a launch-point fire through mxapi | gate |
| smoke | whoami.extended, pod Ready, maxinst exit; creates nothing on PROD | gate |
| api | Playwright request fixture on the test org and site; cancel and close | gate |
| cron, escalation, workflow | agent cron.runNow and CRONTASKHISTORY; a seeded record, the escalation run, ESCSTATUS and COMMLOG; workflow.initiate, assignments, WFINSTANCE | realistic |
| golden | open every application in tests/golden/<SITE>.json inside the shell frame with the current UI session, read the loadapp answer for a crash page or a refusal, then compare title, control types, toolbar, tabs and Select Action entries; on the screens the push changed, open More Search Fields and cancel it, open the saved queries, run the list, open the first record and walk every tab and sub-tab | gate |
| drift | mxr export and a comparison with hot/, before deploy and nightly | report-only |
| perf | k6 on a nightly PM against a stored baseline | report-only |
Built now
One failure code, one runbook, and the backup reference filled in.
mxr cancel <wo> --code <code> moves the work order to CAN and prints the runbook for that code with the work order, the site, the backup reference and the run folder filled in. Step 11 above shows it.
| Code | What it means and where to go |
|---|---|
| BUILD/COMPILE, BUILD/DBC | mvn package failed, or a DBC script failed validation. The site is untouched. Runbook: build-failed. |
| DEPLOY/CR-NOTREADY | the ManageWorkspace stayed short of Ready inside the site timeout. Diagnostics: the condition timeline, the maxinst tail, the operator excerpt. Runbook: cr-notready. |
| DBC/UPDATEDB | maxinst or updatedb failed on a customer script. Fail closed, capture the log tail, print the backup reference. Runbooks: updatedb, rebuild-recovery. |
| DEPLOY/HOT-PARTIAL | the hot push stopped at the first failed item. The applied, skipped and failed lists are in run.json; the re-run is idempotent; the pre-push export becomes a revert work order. Runbook: hot-partial. |
| TEST/GATE, TEST/UI-FLAKY | a gated task failed (exit 2) and the work order holds INPRG for a resume or a cancel; a UI journey that passed on its one retry is recorded as flaky, never as green. Runbook: test-gate. |
| AUTH/INFRA | the API key or cluster access was refused (exit 3), before any browser or deploy started. Runbook: auth-infra. |
Every run folder holds run.json, the archive, the pre-push export, the Playwright reports and traces and, after a failed rebuild, diagnostics.md with the condition timeline, the maxinst tail and the operator excerpt.
Built now
One key. Manage decides what it may do.
Whatever user the key belongs to, Manage's own security decides what it can do. There is no group, user or property to create. Secrets come from environment variables only; the repo, the run record and every YAML stay free of them.
- What the agent touches — Hot artefact tables, and only those. The agent reads hot artefact tables and the history tables the tests assert on, writes to hot artefact tables only, and leaves MAXUSER, APIKEYTOKEN, PASSWORDHISTORY, LOGINTRACKING, MAXSESSION, USERPASSWORD, the MAXADMIN group and every security property exactly as they are. Every call is one audit line in the Manage log.
- What the tests touch — The test org and site, or nothing. Records are created where the site file says and cancelled or closed afterwards; with test: { records: virtual } they are validated through Manage's business objects and discarded, so nothing is saved. PROD smoke creates nothing.
- What the product contains — Build time only. No runtime language model call anywhere in the product. AI is used at build time, to write mxR itself.
Built now
Pipeline as data: nobody edits Tekton YAML.
mxr pipeline render turns mxr.yaml and a job plan into a PipelineRun: one mxr Task per job-plan task, chained with runAfter and gated with when on each task's on flag. Step 10 above shows the render.
- Runner — In the cluster. Tekton on the same OpenShift cluster: the Task can patch the CR, reach the bucket and the Manage routes. GitHub and GitLab are webhook triggers; a push raises a work order with mxr plan.
- Secrets — One per site. An OpenShift Secret per site holds the API key, bucket and approval secret, mounted into the Task as environment variables. The CLI reads env vars only.
- PMs — Scheduled inspections. mxr pm add nightly-drift --cron "0 2 * * *" --jobplan drift writes mxr.yaml and a CronJob that creates a PipelineRun for mxr inspect.
Built now
Compose a job plan in two minutes.
mxr ui serves a static page. Screen 1 is the Job Plan board; screen 2 is the Work Order screen. Saving commits jobplans/<name>.yaml through the GitHub or GitLab API with your own token, so git gives history, review and rollback for free; Save to working tree writes the same file with no Git, and the next mxr run records it as a version. Step 13 above shows both.
- Screen 1 — Job Plan board. A left rail of cards grouped by artefact type and test type, three site columns, drag to add and reorder. Each card has On, Gate and Test toggles; UI cards take app tags. A template picker and Save sit top right.
- Screen 2 — Work Order. Work order number, status pill, work type, site, archive SHA and failure code, the live task checklist from run.json, and Approve, Start and Cancel that call the CLI. It works at phone width.
Pick standard-hot, drag UI into TEST, flip Gate on, switch Perf off in DEV, Save. The next push raises a work order in WAPPR.
Built now
Thirteen commands.
Human-readable output by default, --json for machines, --verbose logs every Manage request as METHOD path → status ms.
| Command | What it does |
|---|---|
| mxr init --site DEV --url … | Write mxr.yaml, the site file and the four job plans. With the key exported it reads the key's user and where that user creates test records. One key is all a site needs. |
| mxr doctor | The key through whoami, MXR.AGENT installed or updated through the Automation Scripts object structure, the key's groups and grants, one work order created and cancelled on the test site (validated and discarded with virtual records), then cluster identity and RBAC asked of the API server itself, the bucket and the admin image for the sites that use them. A site that refuses the test record is named with the attribute, its default and its field class. Pass or fail with the fix on every line. |
| mxr export | Pull the live hot artefacts into hot/ with stable names and sorted keys, following the site's export filters. Drift is what changed since the last recorded version: git diff with Git, mxr history diff without. --all --children reads each record's child rows too, as mxr promote plan does. |
| mxr plan | Diff against the deployed version (the ManageWorkspace annotation, the last completed work order on a site without one, or what mxr export recorded without Git), decide the tier by the path rule, build the archive for a REBUILD, raise the work order WAPPR and print why. |
| mxr promote plan --from DEV --to UAT | Export two sites, list what differs (new, changed, only on the target, excluded) and raise a work order WAPPR with each item's tier, its sha256 and the list hash. --dry-run prints the list; --refresh <wo> makes it again and sends an approved work order back to WAPPR when the list changed. |
| mxr approve <wo> | APPR, with the approver recorded, and for a promotion the hash of the list approved. Required for PROD and for every REBUILD, which also carries a backup reference and a window. |
| mxr start <wo> | INPRG: deploy for the tier, run the job plan, COMP when every gated task passes. The same command on the next site is promotion of the same commit or saved version; without Git it deploys the files of the version the work order recorded, whatever has changed in the folder since. A promotion work order starts on its target, after reading the source again and checking every approved item against its hash. Re-running resumes: every hot item is previewed against the site again and the ones recorded as applied are kept; --dry-run previews everything and writes nothing. |
| mxr inspect --jobplan drift | Tests only, no deploy: an inspection work order. Nightly drift and the weekly UI run are inspections. --promotion <wo> prints a promotion's approved list, its approvals and what was applied; --diff --from UAT --to PROD prints what differs between two sites now. |
| mxr cancel <wo> --code … | CAN with one failure code, then the recovery runbook for that code with the work order, the site and the paths filled in. A rebuild is recovered by a person, from the runbook. |
| mxr pm add … | A scheduled inspection (a PM): the schedule goes into mxr.yaml and a Tekton CronJob next to it. |
| mxr pipeline render | The same run as a Tekton PipelineRun: one Task that runs mxr, one parameter per job plan task, chained in order. |
| mxr ui | The Job Plan board and the Work Order screen, one static page on localhost. Saves are commits through the GitHub or GitLab API with your own token, or a save to the working tree when there is no Git; the next mxr run records it. |
| mxr history list | diff | restore | export-git | The version history mxR keeps in .mxr/history when there is no Git: every version with who, when and what changed; the lines that changed between any two; the folder put back as it was (a dry run unless --apply, and the folder as it is now is recorded first); and one Git commit per version for the day you move to Git. |
What is next
Two more runs on a lab, recorded the same way.
Each is a runnable experiment in the repo with the commands to type and a Result section. MXR_RECORD=1 records every Manage call to a fixture, and the tests replay it, so a run on your lab becomes a regression test.
- S2 · 45 minutes — A Migration Manager change package. One tiny change package on a lab: migmgr.preview compares it before any push and migmgr.deploy runs it through DMService. Presentation export and import are already proven on this run.
- S3 · one rebuild — CR patch and poll on a cluster. A no-op archive change with --verbose prints every condition change with its elapsed time, then a deliberately broken DBC captures the failure shape and the diagnostics.md that goes with it.