MemberJunction / MemberJunction/MJ
Silent realtime-vendor selection, ephemeral magic-link keys, unhelpful class-registration errors, and no owner column on __mj.File
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
Found on 2026-08-17 standing up a full Open App stack (bizapps-ats + bizapps-caliber + bizapps-forms on one MJAPI/MJExplorer pair) and driving a candidate end to end through a realtime voice session. Five findings against MJ core, all diagnostics or platform gaps rather than engine faults.
Common thread: **the platform knew what was wrong and reported something else**, or reported nothing.
---
## 1. Realtime vendor selection returns null with no diagnostic — a resolvable config error looks like nothing
`selectRealtimeVendor` (packages/AI/Agents, `realtime/realtime-vendor-resolution.ts`) picks the highest-priority Active vendor **whose `DriverClass` resolves an API key**, and returns `null` otherwise. Keys resolve as `AI_VENDOR_API_KEY__`, case-insensitively (`AI/Core/src/generic/apiKeyDictionary.ts`).
**The trap:** `OpenAILLM` and `OpenAIRealtime` are **different driver classes**. A host whose LLM prompts work perfectly — key present, models seeded, realtime vendors `Active` in metadata — still has *zero* usable realtime vendors, and nothing says so until a session tries to launch. On this host that was 10 Active realtime vendors (`OpenAIRealtime` ×3, `GeminiRealtime`, `GrokRealtime`, `HuggingFaceRealtime`, `InworldRealtime`), all skipped silently.
`base-agent.ts` has good text for the *downstream* failure:
> Configure a model of AIModelType 'Realtime' with an active vendor DriverClass and a valid API key (e.g. AI_VENDOR_API_KEY__).
…but selection itself logs nothing, so a caller that consumes the null differently (see the caliber case below) never surfaces even that.
**Fix:** when selection returns null, log which vendors were considered and which env var each would have needed. All of that data is in hand at the point of failure. Consider a one-line boot warning when models of type `Realtime` exist but none resolves a key — that is a misconfiguration, not a valid steady state.
---
## 2. An unset `magicLink.rsaPrivateKey` silently downgrades to an ephemeral keypair
`MagicLinkKeys.ts` generates an ephemeral RS256 keypair when no key is configured. The log line exists, but the behaviour deserves louder treatment than an informational default:
- It is a **data-loss-on-restart** condition — every outstanding magic-link session is invalidated whenever the process restarts.
- It makes everything upstream *appear* correct. Magic-link sessions sign and redeem fine, so intake, claim and enrolment all pass.
- Consumers that read `configInfo.magicLink.rsaPrivateKey` **directly** get `''` and fail in unrelated-looking ways. In our stack that produced a GraphQL `BAD_USER_INPUT` about a null variable, which reads as a caller bug and cost an hour.
**Fix suggestion:** promote to a warning that names the restart consequence, and consider exposing "is the magic-link key ephemeral?" in whatever readiness surface exists, so a downstream package can ask rather than reading the raw config value and drawing its own conclusion.
---
## 3. "Dashboard class 'X' is not registered" never says which package provides it
Full text today:
```
Dashboard class 'FormsHomeDashboard' is not registered. Please check the class registration.
```
The actual cause was that `@mj-biz-apps/forms-ng` **was not installed in the Explorer at all** — the app's migrations had run (so the database seeded nav items naming that `DriverClass`), but the Angular client package was absent. The message sends you to inspect a registration that does not exist rather than to install the thing that provides it.
This is a routine state for a multi-Open-App host, and the same message covers three quite different causes: package not installed, package installed but not referenced (tree-shaken), and a genuine key mismatch.
**Fix:** enumerate the keys that *are* registered for the base class in the error (the ClassFactory already does this elsewhere — we saw a `RequiresSubclass=false` message list every registered key), and if possible name the package that MJ expected. Even "no classes are registered for BaseDashboard from any @mj-biz-apps package" would have pointed straight at it.
Related and worth documenting: `mj codegen manifest` walks a package's **dependencies** and never scans the package's own `src`, so a package's own dashboards can never appear in its own manifest — they must be referenced from the consuming app or tree-shaking drops them. That is subtle, correct, and currently only discoverable by reading a comment in a generated file.
---
## 4. `__mj.File` has no owner or scope column, so file access cannot be row-scoped
At 5.51.0, `MJ: Files` read is flat: `CanRead` grants every file instance-wide, and RLS has no column to key a filter on.
For an ATS this blocks properly-scoped recruiter access to candidate résumés and interview audio — a recruiter at company A can read company B's candidate files, and there is no way to express otherwise. It also blocks the anonymous-upload case: nothing distinguishes a file a respondent genuinely uploaded from a row someone fabricated, so a respondent role cannot safely be granted anything on `MJ: Files` at all.
**Ask:** an owner/scope column (or per-row ACL) on `__mj.File` that RLS can filter on, plus a provenance marker for files created through an anonymous upload path.
Filed here rather than worked around because every consumer needs the same thing and a per-app shim would be worse.
---
## 5. Two smaller ones
**Explorer drops query params for `Custom` nav items.** Deep links into a custom dashboard lose their parameters, so a shared or bookmarked URL lands on the default view. Reproduced against a `Custom` nav item pointing at an Open App dashboard.
**`EntityName` is decorative on typed view resolvers.** The generated `RunViewByName` / dynamic-view resolvers accept an `EntityName` in the input and ignore it — the entity is fixed by the resolver. Passing the wrong one returns the *resolver's* entity with no error, so a copy-paste bug reads as working code. Either honour it or reject a mismatch.
---
Corresponding issues from the same session are being filed on bizapps-caliber, bizapps-ats and bizapps-forms.
Contributor guide
Research direction
Read realtime/realtime-vendor-resolution.ts, AI/Core/src/generic/apiKeyDictionary.ts, MagicLinkKeys.ts, base-agent.ts, and the ClassFactory registration paths first. Then trace Explorer Custom navigation, __mj.File/RLS metadata, and the generated RunViewByName resolvers. Done means each reported failure has a useful diagnostic or defined platform behavior, with regression coverage for the affected paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, graphql, typescript
- Domain
- api, backend, database, frontend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100