| id | server-side-tooling-split | ||||
|---|---|---|---|---|---|
| title | Investigation — Server-side vs container-side tooling (thick-server vs thin-server for the inner loop) | ||||
| type | investigation | ||||
| status | accepted | ||||
| created | 2026-05-25 | ||||
| updated | 2026-05-25 | ||||
| tags |
|
Investigation — Server-side vs container-side tooling (thick-server vs thin-server for the inner loop)
Status: Investigation + recommendation → adopted by m-cli-go-toolchain-spec.md §9 · Updated: 2026-05-25
Companion to: vista-iris-dev-bridge-spec.md (core §6.5, §8) · …-public.md · …-va.md · vscode-plugin-design.md · iris-native-source-control-investigation.md
Question driving this doc: How much of the development tooling could be pushed server-side into IRIS — running inside the dev instance (as M, ideally in the standard library) — so that unit testing and the watch/inner-loop run on the development server, with the VS Code plugin connecting from outside? The dev instance is a non-production test/dev VistA, installed once, with no impact on operational systems.
⚠ Framing. This is an analysis of a design fork, not a contract change. The core spec deliberately swung container-ward —
.m-first, file-side tests on YottaDB, thetree-sitter-mstack beside IRIS — and set aside the IRIS-native engine (iris-native-source-control-investigation.md). This document asks the opposite question and finds the inner loop bisects cleanly: the run-and-verify half belongs server-side; the read-understand-edit half does not. Adopted:m-cli-go-toolchain-spec.md§9 adopts the bisection and the server-resident pure-M run/verify half, resolving the six open questions below (§9.1).
- The inner loop bisects along one line: does the work need the live FileMan DD + data or the compiler (→ server-side), or does it need the parse tree (→ container-side)? Everything sorts onto one side or the other (§1).
- Most of the run-and-verify half can live in IRIS — compile, execute, the integration unit-test tier, a watch/compile-test-coverage daemon, and drift detection. The "one-time install on a non-prod dev box" premise removes the only safety objection to putting them there (§2).
- The static half resists the move —
m lint/m fmt/m lspare built ontree-sitter-m(C/WASM). Pushing them into IRIS means re-implementing the M parser in M; that is the expensive part, and it would fork the single parse substrate (§2, vscode-plugin-design §6.1). - The decisive nuance: there is a difference between pushing tooling into IRIS's proprietary
%-library (server-side but locks to IRIS, breaks the YottaDB /.m-first goal) and shipping it as portable pure-M inm-stdlibthat merely runs server-side on the dev IRIS (server-side and still portable). Prefer the latter for everything that moves (§4). - Net effect on the design: if adopted, the VS Code plugin shrinks toward a thin trigger + renderer over a fat dev server — closer to the IRIS-native investigation's original recommendation than to v4. It is a fidelity/simplicity ↔ portability/parity trade, stated honestly in §5.
Every piece of inner-loop tooling sorts by a single test:
Does it need the live runtime (the compiled routines + FileMan DD + data), or does it need the source parse tree?
- Runtime-coupled → wants to run where the runtime is = inside IRIS. The whole reason the core keeps an IRIS integration tier at all is that routine behavior depends on the target's DD + data (core §6.4); that dependency exists only server-side, so the work that exercises it is most faithful there.
- Source-coupled → wants to run where the parse substrate is = the
tree-sitter-mWASM stack in the remote-dev container (vscode-plugin-design §6.1). IRIS has no tree-sitter; moving this work means rebuilding the grammar in M.
So "how much can go server-side?" = essentially the entire run-and-verify half of the loop, plus source control (which the IRIS-native investigation already showed is server-side). Not the read-understand-edit half.
the inner loop, bisected
┌───────────────────────────────────────────────────────────────────────┐
│ read / understand / edit │ run / verify │
│ (source-coupled → CONTAINER) │ (runtime-coupled → IRIS dev box) │
│ │ │
│ • tree-sitter-m highlight │ • compile ($SYSTEM.OBJ.Load) │
│ • m lint / m fmt (72 rules) │ • execute │
│ • m lsp (diag/nav/complete) │ • INTEGRATION unit tests (DD+data) │
│ • vista-meta hover / domain │ • WATCH: recompile→retest→coverage │
│ • the editor UI │ • drift detection (DB↔FS) │
│ │ • source-control round-trip │
└───────────────────────────────────────────────────────────────────────┘
VS Code plugin (thin) the dev VistA-on-IRIS (fat)
| Tooling component | Server-side fit | Why |
|---|---|---|
Compile ($SYSTEM.OBJ.Load/Compile) |
✅ already native | Runs on IRIS by definition (core §6.5) |
| Execute / run a routine | ✅ always | M executes on IRIS |
| Unit testing — integration tier | ✅ strong | Exists because of the live DD+data (core §6.4, §8.2); that only exists server-side (§3) |
| Watch / inner-loop daemon | ✅ good (splits) | IRIS primitives exist (background JOB, work-queue manager, task scheduler, OnAfterCompile/OnAfterSave hooks — investigation §B.1); covers compile+test+coverage, not lint/fmt (§3.2) |
| Drift detection (DB↔FS) | ✅ | Reads the live namespace; per-routine hashes already in the baseline (core §6.3, §14) |
| Source-control round-trip | ✅ already native | git-source-control runs server-side and drives the OS git binary (investigation §B.2) |
| Discovery / seeding | ◐ partly | Discovery probes are server-side queries; orchestration + baseline are the bridge's (core §5–§6) |
| Coverage | ◐ | Server-side if the tests run server-side; LCOV rendering stays client-side |
| Unit testing — fast file-side tier | ✅ keep client | Runs on YottaDB + parser with no transform; gates the PR without IRIS (core §8.2) — don't move it |
Lint / format (m lint/m fmt, 72 rules, taint) |
❌ hard | Built on tree-sitter-m (C/WASM); no tree-sitter in IRIS (vscode-plugin-design §2.1, §6) |
LSP intelligence (m lsp diag/nav/complete) |
❌ hard | Same parse substrate |
VistA domain knowledge (vista-meta TSVs) |
◐ | Could be globals server-side; today precomputed data the editor reads locally (vscode-plugin-design §2.2) |
| Editor UI | ❌ client | It is VS Code |
Caveat on "❌ hard": it is not impossible — VistA's own XINDEX is pure-M static analysis that already runs resident in the namespace, so a weaker version of in-IRIS static analysis exists today. But
tree-sitter-m(99.06% clean on the 39,330-routine corpus, vscode-plugin-design §2.1) is far richer; reimplementing it in M is the cost being weighed, and doing so forks the "one parse substrate" invariant.
The core already defines two test tiers (§8.2):
- File-side, fast —
m test/coverageon the.mtree (parser + YottaDB), no transform, gates the PR. Keep this client-side — it is the cheap, frequent gate and needs no IRIS. - IRIS integration — M-Unit / behavioral tests against the actual DD + data. This is the tier the question is really about.
Today the bridge stages routines and orchestrates the integration tests from outside on each run. The server-side move is to install a resident test harness once into the dev namespace(s), so the tests live where the data is:
- The plugin triggers a run (Atelier REST
POST /action/queryagainst a runner method, or a small custom endpoint) and renders results in VS Code's Test Explorer — trigger + render, nothing more on the client. - The harness runs against the live FileMan DD + data — exactly what cannot be reproduced file-side (core §6.4), which is why this tier belongs server-side.
- Keep both tiers. Per-iteration server round-trips are slower than the YottaDB gate; the fast tier stays the PR gate, the resident tier validates against reality.
Implementation choice matters — see §4. A pure-M / M-Unit-style runner shipped in m-stdlib stays portable (runs on YottaDB and the dev IRIS); IRIS's proprietary %UnitTest does not.
A server-side watch daemon can collapse the round-trip from "save .m → stage → load → run → report" to "the server is already watching." On a routine change it would: recompile → run affected tests → compute coverage → stream results back to the plugin over the same WebSocket transport the Lite Terminal and debugger already use (investigation §A.2, §A.5). IRIS supplies the primitives — background JOB, a parallel work-queue manager, the task scheduler, and the OnAfterCompile/OnAfterSave source-control hooks (investigation §B.1) — to trigger and run it.
But watch bisects exactly like everything else: the static half of "watch on save" — lint and format-on-save — still needs tree-sitter-m. So a server-side watcher covers compile + test + coverage; lint/fmt stay in the container. The developer experiences one loop; it is served by two halves.
"Push it into IRIS" hides two very different moves:
| Move | Server-side? | Portable? | Verdict |
|---|---|---|---|
Into IRIS's proprietary %-library (%UnitTest, %Studio.SourceControl) |
✅ | ❌ — locks to IRIS; breaks the YottaDB / .m-first / public-community goals (core §6.1) |
avoid for the canonical path |
As portable pure-M shipped in m-stdlib, running server-side on the dev IRIS |
✅ | ✅ — same M runs on YottaDB and IRIS | prefer |
m-stdlib is already described as "a pure-M runtime stdlib" (vscode-plugin-design §2.1). A pure-M test runner and a pure-M watch coordinator can ship as part of it and be installed once into the dev instance. This gets server-side execution without paying the portability tax — it is the move that lets the question's instinct and the core spec's .m-first goal coexist instead of conflict.
The IRIS-native investigation already established that IRIS ships the entire round-trip engine server-side (Atelier + isfs + compile + debug + git-source-control + per-item locking) — and v4 set it aside to keep .m first-class and portable. This question is, in effect: reconsider that for the dev box. The trade is real and symmetric:
- Toward IRIS (this question): everything runs where the data is; no
.m⇄.inttransform on the run/test path; one box; fewer moving parts; the integration tier and watch loop become native. Cost: portability and the public/community story erode if the tooling is IRIS-proprietary (mitigated by §4). - Toward the container (v4 today): portable, YottaDB-native, community-shareable, parity by construction. Cost: the transform boundary (core §6.5) and tooling that lives beside IRIS rather than in it.
What the "one-time, non-prod" premise does and doesn't buy:
- ✅ Removes the safety objection. Modifying a dev/test VistA is fine;
git-source-control's ownMapEverywhere()+BaselineExportbootstrap already assume exactly such a one-time server install (investigation §B.2). - ❌ Does not by itself satisfy parity. A server-side component must be installed identically in both profiles to stay [Parity] — which holds, since both the public and VA profiles have an IRIS dev instance (core §9).
- ❌ Does not by itself preserve portability. That is what §4 (pure-M, not
%-library) is for.
- [Parity] (identical both environments): the resident pure-M test harness + watch coordinator version set and their command/endpoint contract; the two-tier test model (fast file-side gate + resident integration tier); the bisection itself.
- [Profile] (differs by environment): whether the dev IRIS host has the OS prerequisites for any server-side git/watch (git ≥ 2.31, callout privileges — investigation §E); the trigger/stream transport endpoint per environment; AI/MCP access to the runner (core §9).
- [Instance] (discovered): the namespace(s) the harness installs into; the routine/test inventory; the DD+data the integration tier runs against (core §5–§6).
| Current spec position | This investigation | Possible resolution |
|---|---|---|
| Integration tests orchestrated by the bridge from outside on each run (core §8.2) | Install a resident test harness in the dev namespace; the plugin triggers + renders | Promote the integration tier to a server-resident, pure-M harness; keep the file-side gate unchanged |
watch is a file-side affordance driven from the container (vscode-plugin-design §8) |
A server-side daemon handles compile+test+coverage; lint/fmt stay client-side | Split watch into a server half (run/verify) and a client half (static) — one loop, two halves |
Round-trip is a bridge-owned .m⇄.int transform at the IRIS boundary (core §6.5) |
Run-and-verify already lives server-side; the transform feeds it | No change to the canonical .m file unit; this is where work runs, not what is versioned |
| Tooling lives beside IRIS in the remote-dev container (vscode-plugin-design §7.3) | Run/verify tooling can live in IRIS as portable pure-M in m-stdlib |
Reconcile via §4: server-side ≠ IRIS-proprietary |
Resolved. All six are answered in
m-cli-go-toolchain-spec.md§9.1; they remain here as the design rationale.
- Pure-M vs
%UnitTest. Is a pure-M / M-Unit-style runner inm-stdlibrich enough for the VistA integration tier, or is IRIS%UnitTestneeded (and the portability cost accepted on the dev box only)? - Watch trigger. Source-control hook (
OnAfterCompile/OnAfterSave), a polling backgroundJOB, or the work-queue manager — which is the robust, low-overhead trigger at the 33,941-routine scale? - Result transport. Reuse the Atelier WebSocket transports (terminal/debug) for streaming watch/test results, or a purpose-built endpoint? How does it map to the VS Code Test Explorer API?
- Coverage server-side. Can a pure-M coverage probe produce LCOV the existing gutter renderer consumes, or does coverage stay a file-side concern?
- Where does
vista-meta's domain model live — precomputed TSVs read by the editor (today) vs. globals queried server-side — and does the watch loop regenerate it on change (vscode-plugin-design §12, TSV-freshness)? - Two-tier drift. With both a fast file-side tier and a resident server tier, how are their results reconciled in the editor so the developer sees one verdict, not two?
- Bridge:
vista-iris-dev-bridge-spec.md(core §5–§6, §6.4, §6.5, §8.2, §14),…-public.md,…-va.md. - Editor surface:
vscode-plugin-design.md(§2.1m-dev-tools/m-stdlib, §6.1 tree-sitter substrate, §7.3 thin-client topology, §8 bridge affordances). - IRIS-native capabilities:
iris-native-source-control-investigation.md(§A.2 Atelier/transport, §A.5 terminal/debug WebSockets, §B.1%Studio.SourceControlhooks, §B.2git-source-control, §E prerequisites/scale). - IRIS frameworks named (not re-verified here):
%UnitTest.Manager/%UnitTest.TestCase, backgroundJOB, the parallel work-queue manager, the task scheduler; VistA XINDEX as the pure-M static-analysis precedent; M-Unit as the community pure-M xUnit.