Assetize

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 thingMaximo nounWhat it carries
A change set or releaseWork OrderWAPPR → 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 environmentSiteDEV, 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 testsJob PlanAn ordered task list. Each task has On, Gate and Test toggles, a budget and app tags. This is what the board edits.
A schedulePMRaises an inspection work order on a cron: nightly drift, weekly full UI, perf.
A failureFailure codeOne 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 onWhat ranWhen
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 order29 to 31 October 2026
Manage 9.2.237 (MAS 9.2, DB2) with Maximo for Aviation and Service Providerthe 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 minutes31 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.tgz

Need 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.tgz

Need Node 22? brew install node.

Install · Linux

npm install -g https://assetize.com.au/mxr/releases/v0.7.1/mxr-0.7.1.tgz

Need 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 changed

From 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 changed

From 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.

StatusReached byWhat it means
WAPPRmxr plan or mxr promote planraised 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
APPRmxr approveor self-approved and recorded where the site has no approval gate
INPRGmxr startdeploy for the tier, then every task in the job plan
COMPgated tasks passthe same work order starts on the next site, same commit
CANmxr cancel --codeone 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 · exit 0
$ 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"
mxr.yaml, after the PM in step 09 was added · exit 0
$ 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
sites/DEV.yaml: the whole site file · exit 0
$ 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 · exit 0
$ 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 passed

Manage 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 · exit 0
$ mxr export --all --only scripts
scripts          2 file(s)
exported 2 file(s) from DEV
drift (git status --porcelain -- hot):
  ?? hot/
The repo after export · exit 0
$ tree -L 2 (after export)
.
hot
hot/scripts
jobplans
sites

hot/scripts: 2 files, e.g. MXR.AGENT.py MXR.AGENT.yaml

Built 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 · exit 0
$ 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(+)
jobplans/standard-hot.yaml: the pipeline and the tests, as data · exit 0
$ 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-only

Built 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 · exit 0
$ 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 DEV

Built 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 · exit 0
$ 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 · exit 0
$ 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)
The deployed endpoint, called over REST · exit 0
$ 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"}
.mxr/runs/WO1001/run.json: the record behind the work order · exit 0
$ 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": "o

Built 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 · exit 0
$ 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 plan

Built 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 · exit 0
$ 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 · exit 0
$ mxr pm list
nightly-drift        0 2 * * *        drift          DEV

Built 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 · exit 0
$ 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: source

Built 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: the deployed sha now comes from WO1001 · exit 0
$ 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 · exit 2
$ 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         pending

A 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: the failure code and its runbook · exit 0
$ 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 · exit 0
$ 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
.mxr/runs: one folder per work order · exit 0
$ 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.json

Built 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 mxR Job Plan board: task cards per site with On, Gate and Test toggles, and the recent work orders with their status
The Job Plan board: tasks per site, gates, and the recent work orders with their status.
The mxR Work Order screen for WO1001: status COMP, tier HOT, the commit, the approval and the result of every job plan task
The Work Order screen for WO1001: tier, commit, approvals and the result of every task.

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.

sites/DEV.yaml: one word on the test line · exit 0
$ grep -n test: sites/DEV.yaml
8:test: { org: EAGLENA, site: BEDFORD, allowCreate: true, records: virtual }
mxr doctor: the test site line now validates instead of creating · exit 0
$ 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: smoke, api, script endpoints and the read-only journeys · exit 0
$ 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
What the site holds afterwards · exit 0
$ 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: 0

Built 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: seven screens re-captured into the baseline (Equipment and Part Number Master are this add-on's names for Assets and Item Master) · exit 0
$ 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
The tests the baseline generates for two screens: one that it opens, three journeys each · exit 0
$ 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 --jobplan golden-deep: ten screens with their journeys, 34 tests, about six minutes · exit 0
$ 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
The same tests against a baseline that expects a menu entry, a dialog title and a tab People does not have: exactly those three fail · exit 1
$ 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 --jobplan golden-smoke: eleven screens opened against the baseline, and one named for the next capture · exit 0
$ 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
tests/golden/DEV.json: what the baseline holds for one application · exit 0
$ 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:

JourneyOne page per testOne shared sessionWhat changed
Work Order Tracking: create, approve, cancel28.8 s14.2 stwo app opens inside the frame instead of two shell reloads
Assets: look up a seeded asset21.6 s14.0 sincludes the run's one sign-in
The whole run73.6 s, 4 shell loads, 902 requests61.2 s, 1 shell load, 749 requestsREA 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.

scripts/rea-report.mjs: a traced run, read with REA · exit 0
$ 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 200

Built 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.

Maximo Automation Scripts application filtered on MXR, listing MXR.AGENT and MXR_DEMO_HELLO
Automation Scripts, filtered on MXR: the agent and the deployed endpoint. Tenant name and user masked.
The MXR_DEMO_HELLO automation script record in Maximo: Jython source, status Active, no launch point
The deployed script record: source, status Active, no launch point.
Maximo Work Order Tracking filtered on the mxr prefix, every record in status CAN
Work Order Tracking on the test site, filtered on the mxr prefix: every record the test journeys created is cancelled.

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 needsmxRAzure DevOps Pipelines · GitHub Actions · GitLab CI · JenkinsMigration Manager, scripts and a person
The unit of changeA 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 deployThe 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 artefactsScripts 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 applyEvery 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 Maximo18 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 stateAnnotations 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 failsOne 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 rebuildMaven 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 MaximoOne 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 pipelineA 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 solutionsThe 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 runsTekton 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.

ChangedTierWhat happensNeeds
hot/**, tests/**, jobplans/**HOTPreview 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)RESTARTMerge 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, archivesREBUILDBuild <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 codeMeaning
0ok
1a task or deploy failed
2a gate blocked the run; the work order holds INPRG
3infrastructure or authentication
4bad 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
The mxR Work Order screen for a promotion work order in WAPPR, DEV to UAT: 5 items to promote (3 new, 2 changed), 2 only on UAT, 1 excluded, each with its type, name and hash, and an Approve button
The administrator's view before approval: the exact list, in four groups, with each item's hash. Sample data on three test sites.

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.

The same promotion work order after mxr start: status COMP, DEV to UAT (done), approved by maximo.admin, with every promoted item marked applied and the job plan tasks passed
After mxr start: the same list, approved by name, every item marked applied.

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
GroupWhat it holdsWhat mxr start does
NewOn the source, missing on the targetPromotes it
ChangedOn both sites, and the source has something to write: an attribute, or a child row to add (+) or change (~)Promotes it
Only on the targetOn the target, missing on the source; for child rows, NAME#child with the rows the target keepsKeeps it as it is
ExcludedMatches promote.exclude in mxr.yaml or the built-in listHolds 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):

PatternWhy 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.yamlMAXADMIN stays as each site has it
hot/securitygroups/**#groupuserGroup members are users, and each site has its own; the grants promote
hot/persongroups/**#persongroupteamPerson group members are people records, and each site has its own
hot/crontasks/**#crontaskinstanceCron 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 own

A 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
Terminal in a folder with no Git: mxr history list shows four versions by ana and sam with the files each added, changed or removed, then mxr history diff -u shows the lines that changed in a script
A folder with no Git: every version, who recorded it and what changed, then the lines that changed.

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.

Terminal: mxr history restore v00002 as a dry run, mxr history export-git writing four commits, and git log showing one commit per recorded version with its author and time
Going back, and moving to Git later: the dry run changes nothing; the export gives one commit per version, with its author and time.
What Git didWith GitWithout Git
Names the version a work order deploysThe commitA version id, v00007-a1b2c3d4e5f6: its number, then a fingerprint of the files
Says which paths changed (the path rule's input)git diff --name-onlyThe two versions compared
Gives an old copy of a filegit showThe bytes saved in .mxr/history/objects
Reports drift after an exportgit statusThe 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.

TaskTypeDefaultsWhat it does
lintscriptson · gateJython compiled on the site itself through MXR.AGENT script.compile, the interpreter that will run it; JavaScript parsed with GraalJS or node
dbcdbcon · gatewell-formed XML, one <sql target> per database platform, dbversion bumped
migmgrconfigon · gateMigration Manager package compare and per-item preview, read-only; conflicts block
deploydeployon · gatethe tier deploy
smokesmokeon · gate · 5mwhoami.extended through the agent, bundle pods Ready, maxinst exit after a rebuild
apiapion · gate · 10mPlaywright request tests on the test org and site; records are cancelled or closed
scripts-fnscripts-teston · gate · 5mPOST /api/script/{name} with the sidecar's tests: block, and a launch-point fire with an assertion
croncron-teston · 5mMXR.AGENT cron.runNow, then CRONTASKHISTORY
esc, wfescalation, workflowoffseed a record, run the escalation or initiate the workflow, assert ESCSTATUS or WFINSTANCE
mifmifon · gate · 8minbound 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
uiuion · gate · 15mPlaywright journeys in Manage classic, filtered by app tags such as @WOTRACK
goldengoldenon · gate · 10mevery 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, driftui-mobile, perf, driftoff · off · onone 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.

  1. live-refresh properties
  2. domains → object structures → endpoints → external systems → channels → integration controls → queues → JMS sequential consumers
  3. conditions → scripts with their launch points
  4. presentations (app XML, lookups, library, menus, sigoptions) → security groups → start centres and Update Start Center
  5. roles, actions, communication templates, calendars, person groups → workflows (new revision, activate) → escalations → cron tasks (activate, reload)
  6. report designs and a report cache refresh → translations → MigMgr packages
  7. 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.

ActionsWhat they do
whoami.extendedthe calling user and groups, bundle, database platform, MAXUPG build, Java version, admin mode; the CLI adds /api/systeminfo
script.compilecompile a Jython source on the site's own interpreter and return the error with its line, so lint fails the way Manage would
echo, describerequest wiring, and the real method list of a server class, one line each to extend the agent
mbo.query, mbo.upsertread 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.deploylist packages, compare a change package before any push, deploy it through DMService
presentation.export, presentation.importread and write an application presentation and refresh the UI cache
cron.runNow, cron.reloadrun a cron instance now, flag instances for reload
workflow.initiate, workflow.complete, workflow.activateroute a record, answer its assignments, activate a new revision
escalation.activate, startcenter.update, report.refreshCache, report.import, maf.publish, property.refreshthe 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.
TypeMechanismVerdict
scriptscompile on the site through the agent, then POST /api/script/{name} with the sidecar's tests and a launch-point fire through mxapigate
smokewhoami.extended, pod Ready, maxinst exit; creates nothing on PRODgate
apiPlaywright request fixture on the test org and site; cancel and closegate
cron, escalation, workflowagent cron.runNow and CRONTASKHISTORY; a seeded record, the escalation run, ESCSTATUS and COMMLOG; workflow.initiate, assignments, WFINSTANCErealistic
goldenopen 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-tabgate
driftmxr export and a comparison with hot/, before deploy and nightlyreport-only
perfk6 on a nightly PM against a stored baselinereport-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.

CodeWhat it means and where to go
BUILD/COMPILE, BUILD/DBCmvn package failed, or a DBC script failed validation. The site is untouched. Runbook: build-failed.
DEPLOY/CR-NOTREADYthe ManageWorkspace stayed short of Ready inside the site timeout. Diagnostics: the condition timeline, the maxinst tail, the operator excerpt. Runbook: cr-notready.
DBC/UPDATEDBmaxinst or updatedb failed on a customer script. Fail closed, capture the log tail, print the backup reference. Runbooks: updatedb, rebuild-recovery.
DEPLOY/HOT-PARTIALthe 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-FLAKYa 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/INFRAthe 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.

CommandWhat 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 doctorThe 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 exportPull 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 planDiff 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 UATExport 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 driftTests 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 renderThe same run as a Tekton PipelineRun: one Task that runs mxr, one parameter per job plan task, chained in order.
mxr uiThe 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-gitThe 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.