MemberJunction / MemberJunction/MJ

CodeGen / migration / MJAPI-startup diagnostics: four failures that report the wrong cause or no cause at all in a multi-app workspace

Open
#3,975 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

## Summary

> ### ⚠️ Status as of 2026-09-02 — re-verified against `next` @ `ca6c2bd79e`
>
> | # | status |
> |---|---|
> | 1 | 🟠 **still open** — narrower now that #2 has landed |
> | 2 | ✅ **RESOLVED on `next`** — `STEP 2(c)` removed; permissions apply to `includedEntities` only |
> | 3 | 🔵 **fix proposed** — draft PR #4171 |
> | 4 | ✅ **RESOLVED on `next`** — per-package try/catch in `loadDynamicAppPackages`, names the package, no longer takes the API down |
>
> Verification detail in the comment below. Happy to close this and reopen item 1 as its own issue if
> that suits the workflow better — say the word.

Four failures we hit in one end-to-end run of a five-app workspace on `next`. They are separate code paths but one shape: **the failure output names something other than the cause, or names nothing at all.** Each cost 20+ minutes that a single line of output would have saved, so they are grouped — the fix for each is small and they are cheapest to do together.

| # | what happens | what it should say |
|---|---|---|
| 1 | 🟠 CodeGen runs an entity's generated **permissions** files even when the object's creation failed, and the GRANT error is the loudest output | the first failure in the batch |
| 2 | ✅ ~~CodeGen **deliberately** generates and executes permissions for *excluded* entities, so one app's missing SQL objects fail every co-installed app's CodeGen~~ | nothing — this should not reach past the exclusion, or should tolerate a missing object |
| 3 | 🔵 an app migration failure is reported as a bare `Transaction has been aborted.` — *draft PR #4171* | the migration file and the first SQL error |
| 4 | ✅ ~~MJAPI refuses to start with a non-`Error` rejection and **zero** output~~ | which `dynamicPackages` entry failed to load, and why |

Environment: MJ `next` @ `e45fde5b2c` (`6.1.0-edge.2`), SQL Server 2022, Node 24.16.0, pnpm 10.33.0, macOS. Workspace holding MJ + `bizapps-{common,tasks,accounting,orders,contracts}`.

---

## 1. 🟠 STILL OPEN — Permissions files are executed for objects whose creation failed, and the GRANT error shadows the cause

> **Narrower than when filed.** The cross-app amplification came from item 2, which is now fixed, so
> this is the within-app case only: an object fails to create, its generated permissions file still
> runs, and the resulting `Cannot find the object` GRANT error is the loudest thing in the output while
> the real first failure is one line among hundreds. `applyPermissions` already collects per-entity
> results, so the information for a first-failure report is in hand.

On `bizapps-common`, the batch that creates `spCreateOrganization` / `spUpdateOrganization` failed. CodeGen then executed the generated permissions files for those procs and ended the run on them:

```
View or function '__mj_BizAppsCommon.vwOrganizations' has more column names specified than columns defined. ← the real cause, one line among hundreds
Cannot find the object 'spCreateOrganization', because it does not exist or you do not have permission.
Error executing permissions file …/__mj_BizAppsCommon/spCreateOrganization.sp.permissions.generated.sql for entity MJ_BizApps_Common: Organizations: Cannot find the object 'spCreateOrganization', …
Error executing permissions file …/__mj_BizAppsCommon/spUpdateOrganization.sp.permissions.generated.sql for entity MJ_BizApps_Common: Organizations: Cannot find the object 'spUpdateOrganization', …
Error executing one or more permissions files in batch starting from index 10
CodeGen exited 1
```

The two GRANT failures and the batch-index line are what an operator sees; the view-compilation error that actually caused it is not called out anywhere.

**Suggested fix:** skip a permissions file whose target object was not created in this run, and surface the **first** failure in the batch as the reported cause (`applyPermissions` already collects per-entity results — `sql_codegen.ts:504-548` — so the information is in hand).

## 2. ✅ RESOLVED — Permissions are generated and executed for *excluded* entities — so one app's break fails every co-installed app

> **Fixed on `next`.** `STEP 2(c)` is gone, replaced by a comment stating the reasoning, and `STEP 4`
> now applies permissions for `includedEntities` only — with the sibling-schema case named explicitly.
> Nothing further needed here. Original report kept below for the record.

This one is deliberate, which is why it is worth a decision rather than a patch. `sql_codegen.ts:258`:

```ts
// STEP 2(c) - for the excludedEntities, while we don't want to generate SQL, we do want to generate the permissions files for them
```

…and STEP 4 (`applyPermissions`, `:504`) then executes every `.permissions` file it finds for the entity list, excluded ones included.

In a single-app database that is harmless. In a **multi-app** database it is not: the excluded entities belong to *other* apps, whose `spCreate*` / `spUpdate*` may not exist yet — and on a fresh multi-app install that is always true for whichever app runs CodeGen first. Observed:

```
bizapps-tasks/SQL Scripts/generated/__mj_BizAppsCommon/spCreateOrganization.sp.permissions.generated.sql → Cannot find the object
bizapps-accounting/SQL Scripts/generated/__mj_BizAppsOrders/spCreateOrderHeader.sp.permissions.generated.sql → Cannot find the object
bizapps-contracts/SQL Scripts/generated/__mj_BizAppsOrders/spCreateEventOrderLine.sp.permissions.generated.sql → Cannot find the object
```

Each of those failed the whole CodeGen run (exit 1), which failed the app's activation.

**Worth stating explicitly, because it is the obvious first guess and it is wrong:** this is **not** a schema-scoping misconfiguration. `excludeSchemas` was correctly set for every app — each app's config excluded every sibling app schema plus `__mj`, verified per app — and `includeSchemas` (`Database/schema-scope.ts`, which resolves a positive scope into `excludeSchemas`) would make no difference, because STEP 2(c) intentionally acts on the entities the exclusion just removed. So no consumer configuration can avoid it.

**Suggested fix — a choice for you rather than a prescription:** either don't emit/execute permissions for out-of-scope entities at all, or treat a missing object in that specific pass as a skip-with-note rather than an error that fails the run. The second keeps whatever the STEP 2(c) behaviour was for (presumably: keep GRANTs current for entities this package does not own) while making it survivable when the owning app has not generated yet.

## 3. 🔵 FIX PROPOSED (#4171) — An app migration failure reported as a bare `Transaction has been aborted.`

> **Draft PR: #4171.** Root cause turned out to be on the consumer side of a good API: Skyway already
> attaches the script, the failed batch's number and line range, the count of batches that committed
> first, and the driver error as `cause` — but `migration-runner.ts` typed the Skyway result with a
> minimal structural interface that omitted the per-migration `Error` field, so all of it was
> discarded and only the run-level string was reported.

The **entire** message for a failed app migration — 12 events for the whole run:

```
[Fatal] migrations failed: Migration failed for schema '__mj_BizAppsContracts': Transaction has been aborted.
```

No migration filename, no SQL error number, no object name. We found the cause only by extracting the baseline and running it by hand:

```
Msg 1767, Level 16: Foreign key 'FK_ContractLine_Product' references invalid table '__mj_BizAppsOrders.Product'.
Msg 1750, Level 16: Could not create constraint or index. See previous errors.
```

**In fairness, other paths do this well** — a later failure in the same file reported `Failed at batch 2/253 (lines 50-71): Violation of PRIMARY KEY constraint 'PK_SchemaInfo_ID' …`, which is exactly right. So the ask is narrow: **the transaction-abort path loses the underlying error; carry the first SQL error and the migration file through it.**

Same run, same shape, from the installation-record path: `Failed to rollback after operation error: TransactionError: Transaction has been aborted.` — the compensation path failing without reporting what it rolled back (this is #3547's territory).

## 4. ✅ RESOLVED — MJAPI refuses to start with no error message at all

> **Fixed on `next`.** Each package load in `loadDynamicAppPackages`
> (`packages/ServerBootstrap/src/index.ts`) is wrapped in its own try/catch that names the package —
> `Open App server package not found (run 'npm install'?): ` for a resolution failure, and
> `Error loading Open App server package :` otherwise — and the loop continues instead of taking
> the API down. Nothing further needed here. Original report kept below for the record.

The **entire** output of a failed MJAPI start:

```
node:internal/modules/run_main:107
triggerUncaughtException(
^
[Object: null prototype] {
Symbol(nodejs.util.inspect.custom): [Function: [nodejs.util.inspect.custom]]
}
Node.js v24.16.0
```

No message, no stack, no code, no package name. The last log line before the crash was `Loading Open App server packages...`. We attached an `uncaughtException` handler that serialises own properties and confirmed the rejected value carries nothing: `IMPORT REJECT: {}`.

Cause, found by importing each registered server package by hand:

```
OK @mj-biz-apps/common-server CLASS_REGISTRATIONS,LoadBizAppsCommonServer,RESOLVER_PATHS
OK @mj-biz-apps/tasks-server LoadBizAppsTasksServer,…
OK @mj-biz-apps/accounting-server CLASS_REGISTRATIONS,…
FAIL @mj-biz-apps/orders-server Cannot find package '…/MJAPI/node_modules/@mj-biz-apps/orders-server/'
OK @mj-biz-apps/contracts-server CreatemjBizAppsContracts…
```

One registered app's server package was not built (no `dist/`) while `mj.config.cjs`'s `dynamicPackages.server` listed it `Enabled: true`. **A single unloadable `dynamicPackages` entry takes the whole API down and reports nothing.**

**Suggested fix:** wrap each package's load in the `Loading Open App server packages…` step and, on failure, report the package name, its declared `startupExport`, and the underlying resolution error — then decide loudly whether to continue without it or refuse. Rejecting with a non-`Error` value leaves a consumer with no first move.

*(The consumer-side half is ours: our tooling enables the `dynamicPackages` entry at registration time, before the app's packages are built, so a failed activation can leave an instance that cannot boot. We are fixing that. But an unloadable entry should be legible regardless of who wrote it.)*

---

## Why these were grouped

Each is a few lines of error handling, all four are in the same "what does the operator see when this fails" surface, and in our run they compounded: #2 produced the output that hid #1, whose cause was a fourth defect entirely (unseeded `EntityField.Configuration` — filed separately as #3970), and #4 hid a plain missing build. Fixing them individually is fine; fixing them as a set is cheaper and the test for all four is the same multi-app workspace.

Happy to split into four if that suits the workflow better — and with two now resolved and one in
review, splitting out item 1 is probably the tidiest end state.

Contributor guide

Open the contributing guide

Research direction

Start by reading applyPermissions in sql_codegen.ts:504-548 and trace how per-entity results are collected when an object creation fails. Reproduce the multi-app CodeGen failure described in the issue, then make the run report the first creation failure instead of later permissions errors and skip permissions for objects not created in that run. No specific test file is named.

Written by the indexing model from the issue text.

Assessment

Tech stack
sql, typescript
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.