fix(admin): name the characters the audit details column left as ids - #215
Conversation
The Details column printed a raw character id where the Target column three cells left resolved the same class of referent to a name, so one row gave two different answers about the same character. Resolution is now keyed off the `details` field name the same way `detailAccountNames` already was, which is what lets a single `access_list.holder_replaced` row name both of the characters it carries. The ids ride the `character` fetch that was already in flight, so the query count is unchanged. Covers every declared action whose payload carries a character id -- nine keys across eight actions -- because a partial fix would leave the remaining raw ids looking like a data problem rather than a rendering one. Character rows are hard-deleted, so an id that no longer resolves falls back to rendering whole, exactly as it did before.
Polish pass on the detail-character-name change: - six comments corrected against the code they cite (the widened fetch is a separate set unioned later, not an in-flight query; holder_replaced's two ids can be equal; subject_mismatch can name a character never held here; NAMESPACE_TARGET_KIND reaches the opposite conclusion and does so deliberately; the step-3 enumeration omitted the new source; labelled's doc example cited a character id no caller passes it any more) - detailCharacterId now round-trips the digit-string branch through String(), so "0090012" and 17-digit strings degrade to the raw-id render instead of confidently naming whichever character they coerce to
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 9 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
Comment |
The audit log's Details column printed raw character ids where the Target
column, three cells to the left on the same row, resolved the same class of
referent to a name.
main → 90000002andProbe Kiddescribed the samecharacter on the same line.
Two halves were missing. The resolver never looked inside
detailsforcharacter ids, so no name was ever fetched for them; and the renderer's
labelledbuilder has no access to resolved names even when they exist.What changed
src/services/audit.ts— aDETAIL_CHARACTER_KEYStable parallel to theexisting
DETAIL_ACCOUNT_KEYS, declaring whichdetailskeys hold a characterid per action. The collection loop reads it into a
detailCharacterIdsset,which is unioned into the existing
characterIdsset before the singlecharacterquery — no new query, the batched-resolution shape is unchanged.Results land on
ResolvedAuditRow.detailCharacterNames, keyed bydetailsfield name rather than by id, because
access_list.holder_replacedcarries twocharacter ids on one row and an id-keyed map could not say which field a name
came from.
src/app/admin/audit/summarize.ts— acharacterRefbuilder that resolvesthrough a new
characterNamesmap, falling back to the raw id. Ninelabelled(...)sites across eight actions switched to it, leading wordspreserved.
src/app/admin/audit/page.tsx— one line threading the map through.Coverage: all eight actions, not just the three visible ones
A partial fix would be worse than none — the remaining raw ids would read as a
data problem rather than a rendering one. Two independent enumerations (an
exploration pass and the
code-reviewer, walking everylogAudit/logAuditIfChangedcall site across 14 service and job files) agreed on thesame nine keys:
account.createdmainCharacterIdaccount.main_changedmainCharacterIdadmin.main_changedmainCharacterIdaccount.mergedcharacterIdadmin.bootstrap_grantedcharacterIdaccess_list.holder_designatedcharacterIdaccess_list.holder_replacedcharacterId,previousCharacterIdtoken.subject_mismatchsubjectCharacterIdTwo keys are deliberately excluded, both of which a key-name heuristic would
have swept in:
access_list.watch_*'saccessListIdis an EVE access-list id(
access-lists.ts:130-135,166-171), andpayout.item_repriced'sitemIdis aloot_itemid (payout-loot.ts:211-220).Resolution is best-effort by design
Character rows are hard-deleted on unlink and reclaim
(
accounts.ts:205,:505), andtoken.subject_mismatchnames a token's EVEsubject that may never have been linked here at all. Both render the raw id,
which is what the column did before. The miss path is deliberately identical to
labelled's — render on any defined value, nothing only when the key is absent— so a row this cannot resolve is byte-for-byte what it was.
One consequence worth naming for review: the column is now time-varying.
A row reads
main → Probe Kidwhile that character is linked and reverts tomain → 90000001after it is unlinked. That matches what the Target columnalready does, but note
character.unlinkedchose the opposite tradeoff andsnapshots
nameintodetails. These rows do not snapshot.Second commit: polish
Six comments were corrected against the code they cite — including one this
diff invalidated itself (
labelled's doc example named a character id nocaller passes it any more), and one that claimed the widened fetch "rides the
already in-flight query" when that query is issued sequentially.
One real fix:
detailCharacterId's digit-string branch now round-trips throughString().Number("0090012")is90012, so a legacy or hand-inserted rowcould have made the summary confidently name a character the payload never
referred to. Failing the round-trip degrades to the raw-id render, which is
honest. This also covers ids past
MAX_SAFE_INTEGER.Verification
npm run typecheck,npm run lint,npm run format:checkall clean.npm test→ 92 files, 1498 tests passing.Every new test was mutation-checked — shown to fail with only the code under
test removed:
AssertionError: expected '—' to be 'main → ?'- {} + { "mainCharacterId": "Zero Padded" }Where to focus review
DETAIL_CHARACTER_KEYSand thecharacterRefcall sites are twohand-maintained lists in different layers. All nine pairs agree today, but a
future
characterRefadded without a matching declaration would render the rawid forever — indistinguishable from a legitimate hard-delete miss. A parity test
can't close this as things stand:
PartisRender & { keys }and carries nobuilder identity, so a
characterRefpart is indistinguishable from alabelledone. Closing it needs a discriminator onPart, which felt likescope this PR shouldn't take.
Separately, out of scope and unfixed:
discord.role_changedwrites apartialkey (
discord-roles.ts:143,:369) thatPARTSdoes not declare, so thoserows render an unexplained
+1 more.