MemberJunction / MemberJunction/MJ
A context-user misconfiguration is announced once per process and then permanently silent
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
**Found by** the PR gauntlet on #4231 (`fix/4209-provisioning-context-user`), 2026-09-04, as
adversarial-review finding 5. **Not a defect** — it is the contract #4231 states and tests. Filed
as an observability enhancement so the trade-off is decided rather than inherited.
### Where
`packages/MJServer/src/auth/principals.ts` — the de-duplication tracker is
`new MJLruCache({ maxSize: MAX_REPORTED_MISCONFIGURATIONS })`, with no `ttlMs`.
`MJLruCache.Set` uses `Number.POSITIVE_INFINITY` for `expiresAt` when `_ttlMs` is 0
(`packages/MJGlobal/src/MJLruCache.ts:59,121`).
### What happens
A misconfigured context user is reported exactly **once per `purpose :: candidate` per process**,
and then never again for the life of that process.
### Why it matters
That is the intended cure for #4209 (which logged one error-level line per magic-link redeem, on
every host taking the default) and it is pinned by `principals.logging.test.ts`. But the far end of
the trade-off is that a host with a genuinely wrong `contextUserForNewUserCreation` logs one line
at whatever moment the first provisioning happens, then runs for weeks attributing every
provisioned user to a fallback principal with no recurring signal. In a rotating log the single
line ages out, leaving a live misconfiguration with no evidence at all.
### Repro
```bash
# Boot MJAPI with userHandling.contextUserForNewUserCreation: 'typo-that-matches-nobody'
# then redeem 10,000 magic links.
# => exactly one "[MagicLink] Configured user 'typo…' matched no user's Name or Email" line, ever.
```
### Evidence
Gauntlet matrix row **SR6**: 25 consecutive `ResolveConfiguredPrincipal` calls with the same
unresolvable candidate produce exactly 1 `LogError`. The base-build twin produced 25.
### Suggested fix
Pass a `ttlMs` to the existing `MJLruCache` — a few hours, or a day — so the condition is
re-announced periodically without returning to per-request volume. `MJLruCache` already supports
it and evicts lazily on read, so no timer is needed.
`principals.logging.test.ts`'s "logs an unresolvable candidate ONCE however many times it is
asked for" case would need `vi.useFakeTimers()` or an injectable TTL to stay meaningful.
Consider making the interval a config value: this is an operator preference, not a constant.
### Definition of done
- [ ] A failing test that reproduces it, then green
- [ ] A persistent misconfiguration is re-reported on a bounded interval, and a burst of requests
inside that interval still produces exactly one line
- [ ] Existing suite and gates green
### Verify by
`cd packages/MJServer && pnpm test` with a fake-timer test that advances past the TTL and asserts
a second line, and asserts no second line before it.
Contributor guide
Research direction
Start in packages/MJServer/src/auth/principals.ts and inspect the MJLruCache implementation at packages/MJGlobal/src/MJLruCache.ts:59,121. Run packages/MJServer's principals.logging.test.ts, then add coverage for repeated reports before and after the chosen TTL. Done means persistent misconfigurations are re-reported after a bounded interval while bursts within that interval still log once, with the existing suite passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, observability
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100