# MCSJ Business-Logic / Service-Layer Recon Report

Date: 2026-07-04
Scope: Read-only static inspection of MCSJ 26.1 JAR contents and decompiled
method signatures. **No live process was touched, no method was invoked
against the running MCSJ.exe, and no write/save operation was attempted
anywhere.** All work was file-based (`jar tf`, `jar xf` to a scratch temp
dir, and `javap -p` against extracted `.class` files, plus raw constant-pool
inspection with `javap -v`).

---

## (a) Inventory of relevant JAR files

MCSJ ships almost its **entire** proprietary business/application layer in a
single fat jar:

| File | Size | Modified | Notes |
|---|---|---|---|
| `C:\Edmunds\MCSJ26.1\core.jar` | 44,929,855 bytes (~42.9 MB) | 2026-03-30 10:26 | **The** application jar. Contains 7,844 `.class` files across ~30 top-level proprietary packages plus `META-INF/services/*` SPI declarations for a newer REST/service layer. This is where every class named in the task's "observed live classes" list (`core.client.McsClient`, `misc.components.EMenu`, `ap.client.fx.EFXPomastPanel`, etc.) actually lives. |

Everything else under `C:\Edmunds\MCSJ26.1\` is either:
- Third-party open-source library jars in `lib\*.jar` (~150 jars: AWS SDK,
  Jackson, Jetty, log4j2, HikariCP, mariadb/mssql JDBC drivers, JavaFX
  ControlsFX, jxbrowser, iText, POI, etc.) — no Edmunds business logic here.
- Bundled JRE jars under `jre\lib\*` and `jre_x64\lib\*` (standard JDK 8
  runtime jars — `rt.jar`, `jfxrt.jar`, `jce.jar`, etc.) — not MCSJ code.
- `InstalledCode.zip` / `mcsjV2026.1Code.zip` (44.6 MB) — this looks like the
  **update/patch delivery mechanism**: `MCSJSrvr.bat` deletes `core.jar` and
  re-extracts it from this zip on server startup if a newer code package is
  staged. Not inspected in this pass (out of scope; same class content as
  `core.jar` is expected).

**Correction to task brief's expected package naming:** there is **no**
`ub.client` / `ub.server` package. The utility-billing module's real
top-level package name is simply **`util`** (`util.client.fx.*` for Swing
UI panels, `util.server.*` for ~640+ business/domain classes). This sits
alongside sibling modules with the same client/server split pattern:
`ap` (accounts payable), `ar` (accounts receivable), `tax`, `payroll`,
`hr`, `escrow`, `permit`, `finance`, `misc`, `core`, etc. — all inside the
one `core.jar`. Class counts per top-level package (from `jar tf`):

```
util: 1329   payroll: 923   misc: 879   finance: 584   tax: 498
core: 469    reengineering: 438  permit: 345  ap: 389   ar: 375
hr: 306      payment: 301   pp: 271     migrations: 198  reqstn: 83
ic: 70       escrow: 67     al: 62      nelco: 32       imaginary: 48
simpdata: 25 documents: 17  mcsjreport: 16  scripts: 11  gov: 3
authentication: 4  sanitize: 1
```

---

## (b) Promising class/package findings

### util.client.fx (Swing/JavaFX UI panels — NOT business logic, ~250+ classes)
Confirms the live-observed `*.client.fx.*` naming convention seen in the
task brief (`ap.client.fx.EFXPomastPanel`) is used identically in the
utility module: `util.client.fx.EFXDeleteBalAdjPanel`,
`EFXAutoCreateAdjBatchCounterDialog`, `EFXBillCalcCounterDialog`,
`EFXCloseUtilityAccountsPanel`, `EFXChangeUtOwnerIdPanel`, etc. These are
pure Swing/JavaFX UI shells; they call into `util.server.*` for the actual
work.

### util.server (business logic — 640+ classes). Most relevant families:
- **Balance / adjustment specific:**
  `DeleteBalAdj`, `RemoteDeleteBalAdj`, `UtAutoBalAdj`,
  `RemoteUtAutoBalAdj`, `UtBalAdjRpt`, `UtBillAdjRpt`, `UtAutoBalAdjErrorListing`,
  `UtBalanceRow`, `UtmastBalanceRow`, `UtPropBalanceRow`,
  `UtproplnBalanceCalculator`, `RemoteUtproplnBalanceCalculator`,
  `UtproplnBalanceRow`, `UtChargeSearch`, `UtchgtrnInq`, `UtAdjustBatchRowDTO`,
  `Utadjbatchid` / `Utadjcode` / `UtadjcodeList`, `UtCalcFinalBillAdj`,
  `RemoteUtCalcFinalBillAdj`.
- **Core account/master record (utility billing "account"):**
  `Utmast` (the utility billing master account — this is the central
  domain object, 433 KB compiled class, hundreds of getters/setters),
  `RemoteUtmast` (its RMI interface).
- **Deposits / payments:**
  `UtApplyDeposits`, `RemoteUtApplyDeposit`, `UtApplyDepositInterest`.
- **Bill calc engine:** `UtbillCalc` (375 KB compiled — the actual bill
  calculation engine), `RemoteUtbillCalc`.
- Every one of the ~150+ `Remote*` classes in `util.server` is a
  `java.rmi.server.UnicastRemoteObject` implementing a matching
  `Remote*` interface (`extends java.rmi.Remote`) — i.e. **the entire
  utility-billing server tier is built on classic Java RMI**, not REST/local
  calls, for anything reachable from the Swing client.
- A newer, parallel **REST/microservice-style layer** also exists
  (`META-INF/services/reengineering.util.service.contract.*`,
  `core.server.restutility.*`) exposing things like
  `IUtLocationService`, `IUtParmtrService`, `IOSAService`,
  `IUtOSAParmtrService` — these look like a newer internal REST API (used by
  "MyTown"/portal-facing web features), separate from the classic RMI path
  the desktop Swing client uses. Not investigated further (lower priority
  for a desktop-UI-bypass use case since the desktop client uses RMI, not
  this REST layer, based on `McsClient` bytecode below).

---

## (c) Decompiled method signatures — most promising classes

All obtained via `javap -p` (JDK 8u492, matching MCSJ's bundled JRE) against
class files extracted read-only from `core.jar` into a scratch temp
directory (never touched the installation or the live JVM).

### 1. `util.server.RemoteDeleteBalAdj` (RMI interface)
```java
public interface util.server.RemoteDeleteBalAdj extends java.rmi.Remote {
  public abstract int deleteBalAdj(java.util.HashMap)
      throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
  public abstract boolean isDeleteOk(char, int)
      throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
}
```
Implementation `util.server.DeleteBalAdj`:
```java
public final class util.server.DeleteBalAdj
    extends java.rmi.server.UnicastRemoteObject
    implements util.server.RemoteDeleteBalAdj {
  public util.server.DeleteBalAdj() throws java.rmi.RemoteException;
  public synchronized boolean isDeleteOk(char, int) throws imaginary.persist.PersistenceException;
  public synchronized int deleteBalAdj(java.util.HashMap) throws imaginary.persist.PersistenceException;
}
```
`isDeleteOk(char, int)` is a **read-only precheck** method — safe candidate
for a future first live experiment. `deleteBalAdj(HashMap)` is the actual
mutating call — DO NOT call in any future experiment without explicit
separate approval.

### 2. `util.server.RemoteUtAutoBalAdj` / `UtAutoBalAdj`
```java
public interface util.server.RemoteUtAutoBalAdj extends java.rmi.Remote {
  public abstract int autoCreateBatch(java.util.HashMap)
      throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
  public abstract java.lang.String getCurrentAutoId() throws java.rmi.RemoteException;
  public abstract java.util.ArrayList getErrorList() throws java.rmi.RemoteException;
  public abstract int getAutoCount() throws java.rmi.RemoteException;
}
```
`getCurrentAutoId()`, `getErrorList()`, `getAutoCount()` are pure read-only
getters — good low-risk candidates. `autoCreateBatch(HashMap)` is the write
path (creates an auto-balance-adjustment batch) — do not call without
approval.

### 3. `util.server.RemoteUtproplnBalanceCalculator`
```java
public interface util.server.RemoteUtproplnBalanceCalculator extends java.rmi.Remote {
  public abstract java.util.HashMap<String,Object> calculateBalance(
      java.lang.String, misc.day.Day, misc.day.Day, misc.day.Day)
      throws java.lang.Exception, java.rmi.RemoteException;
  public abstract java.util.List<String> getLiensByBlq(java.lang.String)
      throws java.lang.Exception, java.rmi.RemoteException;
}
```
Both methods here appear **read-only / calculation-only** (no persistence
side-effects implied by the signature — returns a computed HashMap/List, no
save/write verb). This is arguably the single best "safe first experiment"
candidate: `calculateBalance(propLnId, asOfDay, ..., ...)` looks like a pure
query/calculation that returns balance data without mutating anything.
(Caveat: cannot be 100% certain without inspecting the method body / running
it — treat as "probably read-only, verify with a decompiler like CFR before
calling it live.")

### 4. `util.server.RemoteUtApplyDeposit`
```java
public interface util.server.RemoteUtApplyDeposit extends java.rmi.Remote {
  public abstract int apply(java.util.HashMap)
      throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
  public abstract int getAcctsRead() throws java.rmi.RemoteException;
  public abstract int getTransWritten() throws java.rmi.RemoteException;
}
```
`apply()` is a write (applies deposit interest across accounts); the two
getters are read-only counters, safe to call only *after* a batch job has
already run (not useful standalone).

### 5. `util.server.Utmast` (the core Utility Master Account domain object)
`public final class util.server.Utmast extends imaginary.persist.Persistent
implements util.server.RemoteUtmast, reengineering.core.system.model.ProxyPortalPersistent`

Selected read-only static finder methods (safe candidates — pure lookups):
```java
public static synchronized util.server.Utmast getPersistent(int, int, boolean, java.sql.Connection) throws imaginary.persist.PersistenceException;
public static synchronized util.server.Utmast getPersistent(java.lang.String, boolean, java.sql.Connection) throws imaginary.persist.PersistenceException;
public static synchronized util.server.Utmast getPersistent(java.lang.String) throws imaginary.persist.PersistenceException;
public static synchronized java.util.HashMap<String,Object> getPropertyAndOwnerInfo(java.lang.String) throws imaginary.persist.PersistenceException;
```
Selected read-only instance getters relevant to balances (all `public
synchronized`, no exceptions beyond checked ones, no writes):
```java
public synchronized java.math.BigDecimal getWtr_Budget_Amt();
public synchronized java.math.BigDecimal getWtr_Pend_Dep_Amt();
public synchronized java.math.BigDecimal getSwr_Budget_Amt();
public synchronized java.math.BigDecimal getElc_Budget_Amt();
public synchronized java.math.BigDecimal getOtr_Budget_Amt();
public synchronized java.util.HashMap<String,Object> getDepositBalances(misc.day.Day) throws imaginary.persist.PersistenceException;
public java.math.BigDecimal getTotalBudgetBalance(String, int, misc.day.Day) throws imaginary.persist.PersistenceException;
```
Write/mutation surface on `Utmast` includes the specific "adjustment"
method the task was hunting for:
```java
public int createAdjustment(java.util.HashMap) throws imaginary.persist.PersistenceException;
```
plus ~150 `set*(...)` setters (name/address/phone/email/flags/amounts,
e.g. `setWtr_Budget_Amt`, `setPend_Dep_Amt`, `setBank_Code`, etc.) and
inherited mutation primitives from `imaginary.persist.Persistent`:
`commit()`, `save()` (both `protected`, not directly callable from outside
the class hierarchy without subclass access or reflection), plus dozens of
public `write*Chgtrn(...)` methods that write ledger/change-transaction
records (`writeUtchgtrn`, `writeFnchgtrn`, etc.).

### 6. `util.server.RemoteUtmast` (RMI interface for Utmast — implements `imaginary.persist.RemotePersistent`)
Relevant subset:
```java
public abstract void setAllFields(java.util.HashMap) throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
public abstract java.lang.String isDeleteOK() throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
public abstract java.util.HashMap getDepositBalances(misc.day.Day) throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
public abstract void setPend_Dep_Amt(char, java.math.BigDecimal, java.lang.String) throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
public abstract java.lang.String clearEnergyBalance(java.lang.String, java.lang.String) throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
public abstract int createAdjustment(java.util.HashMap) throws imaginary.persist.PersistenceException, java.rmi.RemoteException;
```
`createAdjustment(HashMap)` and `setAllFields(HashMap)` are exactly the
kind of "write account/adjustment data" entry points the task asked about
— confirmed present, confirmed remote (RMI), confirmed HashMap-parameterized
(so a real experiment would need to reverse-engineer the expected HashMap
key set from the client caller, e.g. via CFR/Procyon on the calling
`util.client.fx.EFX*Dialog` class — not attempted in this recon pass).

### 7. `util.server.UtBalanceRow` (plain read-only DTO, not RMI)
```java
public class util.server.UtBalanceRow implements java.io.Serializable {
  public java.lang.String getServiceType(); public void setServiceType(String);
  public java.lang.String getYear();        public void setYear(String);
  public int getPeriod();                   public void setPeriod(int);
  public misc.day.Day getDueDate();         public void setDueDate(Day);
  public java.math.BigDecimal getBilled();  public void setBilled(BigDecimal);
  public java.math.BigDecimal getBalance(); public void setBalance(BigDecimal);
  public java.math.BigDecimal getInterest();public void setInterest(BigDecimal);
  ...
}
```
This is a clean, simple, already-serializable balance-line-item value
object — exactly the shape you'd want an automation agent to receive back
from a read-only balance query. (No method on `Utmast`/`RemoteUtmast` was
found in this pass that directly returns a `List<UtBalanceRow>` — likely
produced internally inside report classes like `UtChargeSearch` /
`UtchgtrnInq`, both of which are `misc.server.Report` subclasses driven by
a `runReport(ServerPrinter, HashMap)` entry point rather than a simple
getter; would need deeper tracing, e.g. with CFR, to find the exact
caller chain a future read-only experiment should use.)

---

## (d) Client-vs-Server architecture assessment

**Confirmed: MCSJ desktop client (`MCSJ.exe`) talks to business logic
exclusively via classic Java RMI, and the live client JVM holds a
directly-callable RMI stub reference in a normal Java field with a public
getter.**

Evidence chain, all from static bytecode/constant-pool inspection of
`core.client.McsClient` (the exact class already confirmed live-resident in
the running MCSJ.exe JVM per `REPORT.md`'s TREE capture):

1. **Field**: `McsClient` declares
   `core.server.RemoteAppServer server;` (package-private instance field)
   and a second field `static core.server.RemoteAuthServer authServer;`.
2. **RMI lookup bytecode**, inside `McsClient.connect()`:
   ```
   invokestatic  java/rmi/Naming.lookup:(Ljava/lang/String;)Ljava/rmi/Remote;
   checkcast     core/server/RemoteAppServer
   putfield      core/client/McsClient.server:Lcore/server/RemoteAppServer;
   ```
   This is the textbook `java.rmi.Naming.lookup("rmi://host/AppServer")`
   pattern — the client resolves the server stub once at connect time and
   keeps it live in the `server` field for the rest of the session.
3. **Public accessor exists**: `McsClient` exposes
   `public core.server.RemoteAppServer getServer();` and
   `public void setServer(core.server.RemoteAppServer);` — i.e. **no
   reflection is even needed** to obtain the live stub reference from
   within the JVM; a public getter already returns it.
4. **`RemoteAppServer` is a giant RMI facade interface** (`extends
   java.rmi.Remote`) with 300+ methods, including direct utility-billing
   accessors reachable in one hop from the client's already-held stub:
   ```java
   util.server.RemoteUtmast getUtmast(String, boolean) throws PersistenceException, RemoteException;
   util.server.RemoteUtmast getUtmastByLocationId(String) throws ...;
   util.server.RemoteUtmast getUtmastByAlternateId(String) throws ...;
   util.server.RemoteUtOSAParmtr getUtOSAParmtr(String) throws ...;
   util.server.RemoteUtbtufactor getUtbtufactor(String, boolean) throws ...;
   <T extends RemotePersistent> T getRemotePersistent(Class<T>, String, boolean) throws ...;
   ```
   i.e. calling `mcsClient.getServer().getUtmast("someAcctId", false)`
   from inside the same JVM (via our existing jattach/instrumentation
   bridge) would return a live `util.server.RemoteUtmast` RMI stub for a
   specific utility billing account, on which read-only getters
   (`getWtr_Budget_Amt()`, `getDepositBalances(Day)`, etc.) or the
   documented write method (`createAdjustment(HashMap)`,
   `setAllFields(HashMap)`) could then be invoked — over the wire via RMI,
   but initiated entirely from inside the already-authenticated client
   process, with **no UI interaction required**.
5. **Authentication/transport wrapping observed**: a custom
   `authentication.server.AuthenticatedRMIClientSocketFactory extends
   javax.rmi.ssl.SslRMIClientSocketFactory` is present, meaning RMI
   traffic between client and server may be wrapped in an
   application-level auth handshake over TLS sockets — but this is
   transparent to a caller inside the client JVM: since the client already
   holds a working, already-authenticated stub (`server` field populated
   post-login), calling a method on it reuses the same authenticated
   channel/session with no additional login needed. This matches the two
   observed Windows services (`MCSJ Server CHESTERVILLAGENYDB` = the RMI
   server process running `core.server.AppServer`, launched via
   `MCSJSrvr.bat` -> `core.server.AppServer` main class; `MCSJ RMI Server`
   = the RMI registry, launched via `RMISrvr.bat` -> `jre\bin\rmiregistry`).

**Answer to the explicit question "is it RMI-based, and does the client
already hold live RMI stub references?": YES to both.** The client is a
thin(ish) Swing/JavaFX UI shell around RMI calls into a separate server
JVM (`AppServer`, registered via `rmiregistry`); the client JVM keeps the
main facade stub (`RemoteAppServer`) live in a field with a public getter
for the whole session, and drills down into hundreds of narrower `Remote*`
stubs (e.g. `RemoteUtmast`) on demand through that facade. There is **no
purely-local, in-process (non-RMI) business logic path** for utility
billing data — `Utmast`/`UtbillCalc`/etc. all run server-side inside the
`AppServer` JVM; the client only ever sees remote stub proxies implementing
the same `Remote*` interfaces.

### Plausibility verdict: **YES — direct in-process business-logic/RMI-stub invocation from within the client JVM is plausible in principle**, with high confidence, based on:
- A live, already-populated stub field (`McsClient.server`) reachable via
  a **public** getter (`getServer()`) — no private-field reflection even
  required for that first hop.
- A rich, already-cataloged interface (`RemoteAppServer`, `RemoteUtmast`,
  `RemoteDeleteBalAdj`, `RemoteUtAutoBalAdj`, etc.) with clearly named,
  strongly-typed methods (both read-only getters and write/mutation calls)
  discoverable via plain `javap -p` — no full decompilation (CFR/Procyon)
  was actually needed to get usable signatures; interfaces decompiled
  cleanly and completely with `javap -p` alone. CFR was not downloaded
  because it wasn't necessary for this phase — recommend keeping it in
  reserve for tracing method **bodies** (e.g. figuring out the exact
  `HashMap` key/value schema `createAdjustment(HashMap)` expects) in a
  later, separately-approved phase.
- Our existing proven jattach + Java Instrumentation Agent injection
  pattern (documented in `REPORT.md`) already demonstrates the ability to
  run arbitrary code inside this exact JVM and reach live Swing component
  instances by walking the object graph — the same technique trivially
  extends to reaching `McsClient.getServer()` and calling read-only methods
  on the returned stub, since it's just another live Java object reachable
  from a window/frame instance already enumerated in the TREE capture.

---

## (e) Recommended safest next experiment (NOT executed in this task)

A minimal, strictly read-only, low-blast-radius next step for a **future,
separately approved** task:

1. Inject a new agent class (new class name per the `REPORT.md` caveat
   about re-load semantics, e.g. `BizLogicProbeAgentV1`) that:
   - Walks the already-proven live Swing window tree to locate the
     top-level `core.client.McsClient` frame instance (already known to
     exist and be reachable — no new discovery risk).
   - Calls the **public** `getServer()` method on it via a plain Java
     method call (reflection not even required since it's public, but
     reflection is fine too) to obtain the live `core.server.RemoteAppServer`
     stub.
   - Calls **exactly one** clearly read-only, side-effect-free method on
     that stub, e.g.:
     ```java
     RemoteAppServer server = mcsClient.getServer();
     String version = server.getVersion();          // trivially safe, no DB access
     ```
     as a first smoke test that the stub is live and callable, before
     touching anything DB-related.
   - As a second, still read-only step (separate approval checkpoint),
     call a genuine utility-billing finder:
     ```java
     util.server.RemoteUtmast acct = server.getUtmast("<some known test acct id>", false);
     if (acct != null) {
         System.out.println("Name=" + acct.getName()
             + " Balance(deposits)=" + acct.getDepositBalances(someDay));
     }
     ```
     using **only getters** (`getName()`, `getDepositBalances(Day)`,
     `getWtr_Budget_Amt()`, etc.) — never `setAllFields`, never
     `createAdjustment`, never anything from `imaginary.persist.Persistent`
     that mutates state (`save`, `commit`, `writeUtchgtrn`, etc.).
   - Print results to stdout/log only; make no attempt to modify, save, or
     commit anything.
2. Do this against a **non-production / test account ID** where possible
   (the box also has a `TrainDB` directory and `copyLiveToTrain()` /
   `isOkToCopyLiveToTrain()` methods visible on `RemoteAppServer`,
   suggesting a training/sandbox database exists — worth asking whether
   that can be targeted first instead of `chestervillagenydb` production
   data).
3. Only after that read path is proven safe and reviewed, consider (in a
   further separately-approved task) using CFR/Procyon to decompile the
   *body* of `createAdjustment(HashMap)` and its sibling `util.client.fx`
   dialog callers (e.g. `EFXDeleteBalAdjPanel`, the panel that presumably
   calls `RemoteDeleteBalAdj.deleteBalAdj(HashMap)`) to learn the exact
   HashMap key schema needed, before ever attempting a real write call.

**Explicitly not done in this task, per safety instructions:** no agent
was injected, no method was called against the live MCSJ JVM (read or
write), and no reflective invocation of any kind was attempted against the
running process. This report is 100% static file/bytecode inspection.

---

## Appendix: exact commands used (all read-only)

```bash
# Inventory
find /c/Edmunds/MCSJ26.1/ -iname "*.jar"

# List all entries in the one relevant fat jar (44MB)
"<jdk8>/bin/jar.exe" tf core.jar > core_jar_list.txt

# Extract specific class files to a scratch dir (no modification of core.jar)
mkdir /tmp/mcsj_extract && cd /tmp/mcsj_extract
"<jdk8>/bin/jar.exe" xf core.jar util/server/RemoteDeleteBalAdj.class ...

# Decompile method signatures only
"<jdk8>/bin/javap.exe" -p -classpath core.jar util.server.RemoteDeleteBalAdj

# Inspect constant pool / bytecode for RMI lookup evidence (still read-only)
"<jdk8>/bin/javap.exe" -v core/client/McsClient.class | grep RemoteAppServer
```

JDK used for `jar`/`javap`: `C:/MCSJAgent/SwingBridge/jdk8-extracted/jdk8u492-b09/bin/`
(matches MCSJ's own bundled JDK 1.8.0_352-family runtime closely enough for
clean bytecode-version compatibility; javap reported "1.8.0_492" tool
version, no version-mismatch errors were encountered on any class file).
