MemberJunction / MemberJunction/MJ

A context-user misconfiguration is announced once per process and then permanently silent

Open
#4,237 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.