Relay Lite is now served directly by IRIS after installing iris-relay 0.4.1 from IPM: open /relay/index.html over HTTPS (HTTP is allowed only on loopback). It lists log sources, pages through messages.log and rotations, and provides lexical similarity search over a bounded IRIS Vector Search index. The Lite UI needs no Node.js or Python server; the full management UI remains available through the existing Compose/Node setup.
The page requires an IRIS user with %Admin_Operate:U; search SQL permissions and index limits are documented in README. Credentials are blocked outside HTTPS or loopback, stale requests cannot overwrite a newer session, and /relay serves static files with CSP/automatic compilation disabled.
Reviewed source: 34ed98984f15fec85d54036104f1c93d9cdd474d. GitHub CI: 46 JavaScript and 29 Python tests passed. Claude tested local IPM load/uninstall and 15 browser groups in an isolated cloud IRIS; public registry installation will be checked independently after this release. The existing public demo is unchanged.
AI disclosure: Claude implemented and tested Relay Lite; Codex reviewed and published the change.
An operations workspace for InterSystems IRIS 2026.2. Investigate an instance, make a management change safely, and leave a clear handover for the next operator.
IRIS Relay is a web UI over the IRIS SysAdmin REST API v2, plus a small IRIS extension (ObjectScript REST class and an Embedded Python log reader). Install it with IPM and IRIS itself serves the whole UI, with no Node.js or Python server; or run it with Docker Compose or Node. Every change is reviewed first, re-checked right before it is applied, applied once, and then read back from IRIS. A baseline comparison and a Markdown/JSON export show what changed during the shift.

docker compose up -d starts IRIS 2026.2 and Relay in about 2 minutes (Quick start). On an IRIS you already have: zpm "repo -r -n registry -url https://pm.community.intersystems.com/" (once, on a fresh IPM 0.9 or newer) and zpm "install iris-relay", then open /relay/index.html on that IRIS (IPM).| Judging criterion | Where to look |
|---|---|
| Complexity | Every change is reviewed, re-checked, applied once and read back from IRIS (How a change works). Log similarity search runs inside IRIS with Embedded Python and IRIS Vector Search (New in 0.4). |
| Clarity of instructions | One-command Quick start, a 5-minute tour and the full reviewer script in https://github.com/rafaorlando3/iris-relay/blob/main/docs/DEMO.md. |
| Developer experience | No runtime npm dependencies, 69 JavaScript and 36 Python tests (Validation), and an IPM package that installs the full UI, served by IRIS with no Node.js (IPM). |
| Applicability | Shift handover with baseline comparison and Markdown/JSON export; archived messages.old_* logs from Community idea DPI-I-966. |
| Usability | Before/after preview on every change, a plain “Applied and verified in IRIS” status and a guided tour on a labelled incident (At a glance). |
Technology bonuses and their evidence: https://github.com/rafaorlando3/iris-relay/blob/main/docs/CONTEST.md. What Relay does not do: Limitations.
IRIS Relay was built for the people who operate IRIS: to investigate problems, make changes safely and leave a clear shift handover. It brings diagnosis, state comparison and the history of changes into one flow, so less time goes into collecting information during an incident.
Our biggest lesson was not to trust the API response alone. On IRIS 2026.2 the task list still reported the old Suspended value right after a successful change, while the task detail endpoint reported the new one. Since then Relay checks the state before and after every change, and only reports “verified” when IRIS confirms it.
Requirements: Docker with Compose. Nothing else is installed on your machine.
git clone https://github.com/rafaorlando3/iris-relay.git
cd iris-relay
docker compose up -d
docker compose exec iris cat /usr/irissys/mgr/relay/lab-credentials.json
Open http://127.0.0.1:8787 (use 127.0.0.1, not localhost) and sign in with username and password from that file. RelayObserver with observerPassword has only the %Operator role, so you can also see how IRIS limits a restricted account.
The lab also publishes the IRIS web server on 127.0.0.1:52773:
| What | URL |
|---|---|
| IRIS Management Portal | http://127.0.0.1:52773/csp/sys/UtilHome.csp |
| Relay served by IRIS (the IPM version of the UI, no Node.js) | http://127.0.0.1:52773/relay/index.html |
Default lab login: SuperUser / SYS (local lab only). The official IRIS image ships its predefined accounts (SuperUser, _SYSTEM, Admin and the others) with the password SYS, already expired. The lab lifts that expiry once, when the container is created (##class(Security.Users).UnExpireUserPasswords("*"), as the community templates do), so the Management Portal and Relay accept SuperUser / SYS without a change prompt. That is a well-known password: use it only in this disposable lab on your own machine, and change it (or use the generated RelayLab account above) as soon as the lab is reachable by anyone else. The public demo and the IPM package never do this; the IPM install creates and changes no users.
Compose starts the pinned IRIS Community 2026.2 container, runs scripts/bootstrap.py inside it (Relay extension, lab accounts with generated passwords kept inside the container, disposable demo fixtures, the predefined accounts unexpired and the Relay UI served by IRIS at /relay) and starts Relay once IRIS reports ready. Both ports are published on 127.0.0.1 only. IRIS_BIND=0.0.0.0 docker compose up -d publishes the IRIS web server on every interface: it is plain HTTP with the well-known SuperUser / SYS login, so do that only on a trusted network and change the password first (the IRIS-served Relay also refuses to sign in over plain HTTP on a non-local address). IRIS_PORT=52774 docker compose up -d uses another host port if 52773 is taken. Stop with docker compose down (the next up creates a fresh lab with new passwords).
Expired passwords at the Relay sign-in. If IRIS requires a new password for the account you sign in with (an expired password, or “change password at next login”), Relay says so and offers the change right on its sign-in page: current password, new password twice, then it signs you in with the new one. Try it in the lab with a disposable account, for example in docker compose exec iris iris session IRIS -U %SYS: set sc=##class(Security.Users).Create("RelayTry","%Manager","Try-it-123") kill p set p("ChangePassword")=1 set sc=##class(Security.Users).Modify("RelayTry",.p), then sign in to Relay as RelayTry / Try-it-123. How it works.
Requirements: Docker, Node.js 22 or newer and Python 3. No npm packages and no paid service. Use this path for the repeatable live checks and the log rotation helper.
python3 scripts/lab.py # disposable IRIS container, credentials in artifacts/lab-credentials.json
npm start # IRIS Relay on http://127.0.0.1:8787
Both paths run the same scripts/bootstrap.py inside the container. Stop with Ctrl+C and docker stop iris-relay-2026-2.
Suggested 5-minute tour: Scheduled tasks → Capture baseline → Suspend Relay demonstration task → Confirm → Compare changes → Resume it. Then open Log investigation and pick an archived messages.old_* file (with the lab script, run python3 scripts/lab-rotate-log.py first to create one). Finish with Export handover. The full reviewer script is in https://github.com/rafaorlando3/iris-relay/blob/main/docs/DEMO.md.
From 0.5.0 the IPM package installs the whole IRIS Relay web UI and IRIS serves it. Nothing else runs: no Node.js, no Python server, no extra container.
USER> zpm "repo -r -n registry -url https://pm.community.intersystems.com/"
USER> zpm "install iris-relay"
The first line is needed once on a fresh IPM 0.9 or newer, which comes with no registry configured; it adds the public community registry.
Then open https://<your IRIS host>/relay/index.html, or http://localhost:52773/relay/index.html on the IRIS machine itself, and sign in with your IRIS account. It is the same UI as the Node version, with every area: system overview, web applications and the REST explorer, permissions, security, tasks, log investigation with the similarity search, audit, baseline comparison and the handover export. To try a local checkout instead: zpm "load /path/to/iris-relay".
How it works. /relay is a static-files-only web application: CSP/ZEN class pages and auto-compile are off, and /relay/ alone returns 404 because IRIS does not list folders. The page runs the Relay API in the browser with the same modules the Node server uses (review, re-check right before applying, apply once, read back, allowlisted explorer operations, a 2 MiB response cap, timeouts and redaction) and calls IRIS on the same origin: the SysAdmin REST API at /api/admin and the package’s /api/relay endpoints, as your own IRIS account. IRIS stays in charge of authorization.
Credentials. The page keeps your password in memory only, never in browser storage or cookies, and sends it to IRIS as HTTP Basic on each request. So it signs in only over HTTPS, or over plain HTTP when the page was opened on the IRIS machine itself (localhost, 127.0.0.1 or [::1]); on any other plain-HTTP address the sign-in is disabled and nothing is sent. Signing out, or 30 minutes after sign-in, drops the credentials and cancels the requests in flight; reloading the page also signs you out. The page does not run inside a frame.
What the account needs.
/api/admin (the default on IRIS 2026.2).%Manager covers every Relay view and change. With less, IRIS refuses what the account may not do, and Relay marks those views Restricted and shows the reason.%Admin_Operate:U, plus the database role of the namespace where you installed the package (for example %DB_USER for USER), because /api/relay runs there.SELECT on Relay.LogLine, and INSERT and DELETE to refresh the index, for example GRANT SELECT, INSERT, DELETE ON Relay.LogLine TO <user> in that namespace. Repeat the grant after a reinstall, because the table is recreated. The similarity is lexical (hashed words and character trigrams, no language model), over the bounded index described in New in 0.4 (3,000 recent lines per file, 20,000 in total, queries up to 200 characters).What gets installed. Relay.Api (also /api/relay/account, which tells a signed-in caller its own username and, in %SYS, why a sign-in was refused), Relay.LogReader and Relay.LogLine in the current namespace; log_reader.py and log_vectors.py in <manager directory>/relay/; the UI files in <IRIS install directory>/csp/relay/; the /relay web application (static files) and /api/relay (password authentication, and every endpoint also checks %Admin_Operate:U). It creates no users and no demo data. zpm "uninstall iris-relay" removes the classes, both web applications and the files; the similarity index data stays in the globals ^Relay.LogLineD and ^Relay.LogLineI (remove with kill ^Relay.LogLineD, ^Relay.LogLineI if you do not reinstall), and IPM keeps its own download copy. Relay Lite, the single page from 0.4.1 with the log views and the similarity search only, stays at /relay/lite.html.
Expired passwords. When IRIS requires a new password for your account, the sign-in page offers the change, the same way as the Node version (how it works). If the package is installed in a namespace other than %SYS (for example USER), IRIS refuses a failed sign-in there before any package code can run, so the page cannot tell an expired password from a wrong one: it then offers Change password next to the sign-in error, IRIS still checks everything, and the page links to the Management Portal. In that case it cannot show the IRIS reason for a refused new password either.
Only in the Node version: the public demo mode (a shared account limited to the Relay demonstration objects), the sign-in throttle per client address, a session that survives a page reload (an HttpOnly cookie), and a connection to IRIS on another host: IRIS_URL=https://your-test-instance.example npm start.
The files in web/relay are built from public/ and the root modules by npm run build:iris; a test fails if they drift from their sources.
| Contest area | In IRIS Relay |
|---|---|
| Manage web apps and explore REST APIs | Enable or disable custom web applications with review and readback. REST explorer with 28 allowlisted, read-only SysAdmin API operations, HTTP status, timing and export. |
| Permission management | Inspect roles and their holders, edit custom role resource permissions, replace a user’s direct roles. Built-in accounts, %All holders and escalation roles are protected. |
| Security and secrets | Wallet collection access policies (secret values are never read), X.509 credential owner lists, OAuth resource servers (issuer, audiences, scope, status), TLS configurations (TLS 1.2/1.3 bounds, peer verification cannot be weakened). |
| Task management | Scheduled tasks and task history. Suspend or resume user tasks with state verified through the detail endpoint. System tasks are protected. |
| Operating system | System overview, resources, processes, devices and journal files. |
| Log monitoring and reporting | Embedded Python reader for messages.log, System Monitor and alert logs, including archived messages.old_* files (0.3), with backward paging and page search. Similarity search across all of those files with IRIS Vector Search (0.4). Background audit queries. Markdown/JSON handover exports. |
IRIS does the change; Relay never sets a password itself and keeps no copy of the old one.
/api/relay/account first. On a plain HTTP Basic request IRIS answers an expired password with the same empty 401 as a wrong one, but it gives the reason (#935, “password change required”) to the web application’s login handler, and only when the password was right. Relay.Api answers that handler with a JSON reason for /account only: password-change-required, or denied for everything else (wrong password, unknown or disabled account), so it tells no more than a correct password already proves. A refusal there is final: a wrong password costs one IRIS login attempt, not two.IRISUsername, IRISOldPassword, IRISPassword, form-encoded, never in a URL) to /api/relay/account. The IRIS web login handles them before any Relay code runs: it checks the old password again, applies the password rules of the instance (PasswordPattern and any validation routine) and changes the password, exactly as the Management Portal’s own change page does. A rule violation comes back verbatim, for example ERROR #845: Password does not match length or pattern requirements.Boundaries: the endpoint changes only the account whose current password IRIS has just accepted, so it cannot change anyone else’s password; the public demo refuses password changes; the IRIS-served page keeps its HTTPS-or-loopback rule for this request too; and nothing is logged. Without the Relay extension (IRIS_URL to an instance that does not have it), Relay cannot change the password and links to the Management Portal, which offers the change when you sign in there.
IRIS remains responsible for authorization: Relay signs in with the operator’s own IRIS account and never elevates privileges.

Type what you are looking for (“journal switch”, “license exceeded”) and Relay finds lines with similar wording across the current logs, the numbered rotations and the archived messages.old_* files. Each line is embedded inside IRIS (Embedded Python) and stored in Relay.LogLine with a VECTOR(DOUBLE, 256) column; the query is ranked with VECTOR_COSINE in IRIS SQL, and repeats of the same message are grouped with how often and in which files they occur. The embedding is lexical (hashed words and character trigrams), so it needs no model download and no data leaves IRIS. The index is a bounded cache (3,000 recent lines per file, 20,000 in total) refreshed incrementally: unchanged files are skipped. Searching needs SQL SELECT on Relay.LogLine and refreshing needs INSERT and DELETE; a %Operator account gets an explicit “not privileged” answer. Details in https://github.com/rafaorlando3/iris-relay/blob/main/docs/LOGS.md.

When messages.log grows beyond MaxConsoleLogSize, IRIS renames it (for example messages.old_20260928, then messages.old_20260928_1) and the Management Portal log view shows only the current file (the gap described in idea DPI-I-966). Relay lists the archived files with size and last-write time, newest first, and reads them with the same paging and search as the current log. The browser sends a validated ID, never a path; links and special files are refused. Details in https://github.com/rafaorlando3/iris-relay/blob/main/docs/LOGS.md.
To try it, run python3 scripts/lab-rotate-log.py with the lab running. It produces a real rotation in the lab container and then restores the original setting. It only acts on the container recorded by scripts/lab.py in artifacts/lab-setup.json. A lab created before 0.3 has no record and is refused: confirm its full ID with docker inspect -f '{{.Id}}' iris-relay-2026-2 and run python3 scripts/lab.py --adopt-container <full ID> once.
flowchart LR
B[Browser] -->|same-origin, CSRF token| R["IRIS Relay
Node.js 22, loopback"]
R -->|Basic auth as the operator| A["/api/admin/v2
SysAdmin API"]
R -->|Basic auth as the operator| X["/api/relay
Relay.Api + Relay.LogReader"]
X --> P["log_reader.py
Embedded Python"]
A --> I[("InterSystems IRIS 2026.2")]
P --> I
src/Relay) is installed by scripts/lab.py; see “Connect to an existing test instance” below to install it elsewhere.Installed with IPM, IRIS serves the UI and the Relay API runs in the browser, with the same modules (routes.mjs and the logic it uses; iris-api.mjs is the browser side):
flowchart LR
B["Browser
Relay UI and Relay API"] -->|static files| W["/relay
static files only"]
B -->|Basic auth as the operator, same origin| A["/api/admin/v2
SysAdmin API"]
B -->|Basic auth as the operator, same origin| X["/api/relay
Relay.Api + Relay.LogReader"]
X --> P["log_reader.py
Embedded Python"]
W --> I[("InterSystems IRIS 2026.2")]
A --> I
P --> I
Docker (pinned official IRIS Community 2026.2 image, one-command docker compose lab), Embedded Python (log reader and embeddings running inside IRIS), IRIS Vector Search (VECTOR column and VECTOR_COSINE), an IPM package that installs the full UI, served by IRIS, and the IRIS side (module.xml), the SysAdmin REST API v2, and the Community Opportunity idea DPI-I-966. The live demo runs a real disposable IRIS instance; the separate GitHub Pages walkthrough uses fictional data.
npm test # 69 JavaScript tests
npm run test:logs # 36 Python tests (log reader, similarity index, lab helper, demo incident, lab options)
With the lab and Relay running, python3 scripts/verify-management.py and python3 scripts/verify-enhancements.py apply and restore real changes on the lab fixtures, check replay and permission denials, all explorer operations, audit queries and every log source (including archived rotations). Verified on IRIS Community 2026.2 Build 221U on ARM64 (September 19) and x86-64 (September 28). The lab basics (IRIS port, unexpired lab accounts, password change at the Relay sign-in) were checked end to end on October 2. The 0.5.0 IPM path was checked end to end in Chromium on a fresh IRIS 2026.2 (install, every UI area, changes read back from IRIS, uninstall). See https://github.com/rafaorlando3/iris-relay/blob/main/docs/VALIDATION.md.
This is an experimental tool for authorized administrators, not a replacement for the whole Management Portal and not a production readiness claim. It does not edit secret values, import or rotate keys, create roles, test a full OAuth provider flow, terminate processes, parse journal contents or read every subsystem log. Session history is not a durable audit. In the IPM version, reloading the page signs you out and a session lasts at most 30 minutes. Page search applies to the loaded page; similarity search covers the bounded index described above and ranks by wording, not meaning.
Start Docker, then from this directory run:
python3 scripts/lab.py
npm start
Open http://127.0.0.1:8787. The setup script prints the local path to artifacts/lab-credentials.json. Use the generated RelayLab username and password to connect. The RelayObserver user uses observerPassword and the built-in %Operator role for a limited-access demonstration. Do not publish this file.
The script also creates a disposable Relay demonstration task scheduled for the following day, so reviewers can test suspension and resumption without touching system tasks. It also creates a disabled /relay-demo application and a disabled RelayDemoUser account with no roles for application and permission demonstrations. The demo account has a generated password that is not retained and cannot be used to sign in. Its login remains disabled during role tests. A RelayDemo wallet collection is created with both edit/use access protected by %Admin_Wallet:USE; it contains no real secrets. The script also creates a public, self-signed RelayDemoCertificate owned by RelayLab, with no imported private key, and a disabled RelayDemoOAuth resource server using https://relay-demo.invalid. No application is attached and no external identity provider is contacted. The unused generated certificate key is discarded.
The lab binds IRIS to 127.0.0.1:52785, with a two CPU and 2 GiB memory limit. Relay binds only to 127.0.0.1:8787. The script refuses to repurpose an existing container without its expected image and local setup record. It does not remove containers or data.
Stop the application with Ctrl+C and stop the laboratory with:
docker stop iris-relay-2026-2
IRIS_URL=https://your-test-instance.example PORT=8787 npm start
The URL is an operator-configured origin, not an arbitrary browser-provided proxy destination. HTTP is accepted only for loopback targets; remote targets require HTTPS with valid certificates. Existing instances need Basic authentication on /api/admin. Enter an account with only the privileges needed for your work. Relay does not elevate privileges.
The optional runtime log extension consists of src/Relay/Api.cls, src/Relay/LogReader.cls and src/Relay/log_reader.py. Copy the Python file to <IRIS manager directory>/relay/log_reader.py and load both classes into %SYS. The local setup installs it in %SYS as /api/relay, with password authentication and a %Admin_Operate:U check. Without the extension the runtime log view reports unavailable; the standard management views still work.
Choose Scheduled tasks, capture a baseline, and choose Suspend or Resume on a user-defined task. Review the task and connected instance, then confirm. Relay checks the state again and verifies the result through /v2/task/info. No system task can be changed through Relay. No process termination, data deletion or credential rotation is offered in this build.
The 2026.2 laboratory returned an outdated Suspended value from /v2/tasks immediately after a successful change. Relay reads user-task state from the detail endpoint before display, preview and confirmation. An accepted HTTP response alone is not treated as verified completion.
In Web applications, select Manage on /relay-demo, change the status and select Review change. Confirm only after checking the target instance and before/after values. Only Enabled is sent to IRIS. Restore the disabled status after the demonstration. System applications, applications in %SYS and Relay’s own connection are protected.
In Users, open RelayDemoUser. Choose an existing role such as %Operator, review and confirm the assignment, then remove the role again. The account stays disabled throughout. Only Roles is sent; profile, password and escalation assignments are preserved. In Roles, select Manage to inspect resources, inherited roles and escalation settings. Custom non-escalation roles also support reviewed description and resource-permission edits, with an impact inventory of up to 100 holders.
The current signed-in account, recognized built-in accounts and direct %All holders are protected. Direct %All and %Manager grants and escalation-only grants are not supported. Other roles may still confer powerful or inherited access: this is a tool for authorized administrators, not a role sandbox. IRIS enforces %Admin_Secure. Changes to the target configuration or the selected role definitions invalidate the preview. These checks are optimistic, not an atomic IRIS transaction.
REST explorer provides 28 documented, allowlisted GET operations with validated parameters. It shows HTTP status, elapsed request time, observed timestamp and the IRIS response. List requests are capped at 100 rows. Export the result as Markdown or JSON for handover. It cannot send arbitrary URLs, headers or write methods. Responses are direct API observations; for example, the task-list endpoint may still have the version-specific state lag described above.
For the laboratory demonstration choose Wallet collection policy, enter RelayDemo, and run the request. Then open Wallet collections and Manage. Change UseResource to %Admin_Operate:USE, review and confirm, and restore %Admin_Wallet:USE after the demonstration. The collection is empty: no real secret changes hands. Both resource names are checked against IRIS and any public permission on either resource blocks the change. The administrator needs the IRIS permissions to inspect security resources as well as manage the wallet. Secret values are not read, stored or edited by Relay.
Open X.509 credentials and Manage on RelayDemoCertificate. Add RelayObserver to the selected owners, review and confirm, then restore only RelayLab. Empty owner lists are refused because IRIS treats an empty list as access for all users. Anonymous accounts cannot be added. Relay changes only OwnerList; it does not import or rotate a certificate or its key.
In OAuth resource servers, Manage RelayDemoOAuth, enable the stored configuration, then restore Disabled. The form also supports description, existing issuer, audiences and required scope; leave those fields unchanged for this availability demonstration. This isolated example demonstrates configuration management, not successful token validation or a working connection to a provider.
Open Audit log, optionally filter by user and server-local begin/end time, and choose a maximum of 100 records. IRIS creates a background query. Relay briefly polls, then offers Check query status if it is still unfinished. Pending results are never presented as an empty completed log. Completed rows show event, timestamp, user, description and namespace, and can be exported. Detailed event payloads, session identifiers and network fields are excluded. Free-text summaries still require review before sharing.
Capture baseline, refresh or make a reviewed change, then select Compare changes. Add context under Handover notes. Export handover produces a readable Markdown file; JSON preserves the structured response. Snapshots and notes stay in the browser tab and are cleared on sign-out. The session activity includes the last 100 accepted changes and their readback status. They are not persisted across a browser reload.
Results are capped at 100 records for list views. Each text-log page is capped at 64 KiB and 150 complete lines. Filters apply only to the loaded data. These are operational observations, not an exhaustive audit. Free text may contain sensitive information even though credential-like object fields are redacted. Review exports before sharing them.
With the isolated lab and Relay running on the default ports, run python3 scripts/verify-management.py. It temporarily changes the named demonstration application, account roles, wallet policy, certificate owners and OAuth availability, restores them, checks replay and permission denial, and exercises all 28 explorer operations plus an asynchronous audit query. It verifies that Relay targets the laboratory URL before making changes. Generated results stay under ignored artifacts/.
With the isolated laboratory and Relay running, execute python3 scripts/verify-enhancements.py. This exercises and restores RelayDemoTLS, the unassigned RelayDemoRole and RelayDemoOAuth, checks replay and observer denials, and checks available versus absent log sources.
Contest announcement · SysAdmin API specification · Technology bonuses · Idea DPI-I-966
MIT. InterSystems IRIS is a separate dependency with its own terms. This project is not an InterSystems product.