Skip to content

fix(admin): name the characters the audit details column left as ids - #215

Merged
guarzo merged 2 commits into
mainfrom
worktree-audit-detail-character-names
Aug 10, 2026
Merged

fix(admin): name the characters the audit details column left as ids#215
guarzo merged 2 commits into
mainfrom
worktree-audit-detail-character-names

Conversation

@guarzo

@guarzo guarzo commented Aug 10, 2026

Copy link
Copy Markdown
Owner

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 → 90000002 and Probe Kid described the same
character on the same line.

Two halves were missing. The resolver never looked inside details for
character ids, so no name was ever fetched for them; and the renderer's
labelled builder has no access to resolved names even when they exist.

What changed

src/services/audit.ts — a DETAIL_CHARACTER_KEYS table parallel to the
existing DETAIL_ACCOUNT_KEYS, declaring which details keys hold a character
id per action. The collection loop reads it into a detailCharacterIds set,
which is unioned into the existing characterIds set before the single
character query — no new query, the batched-resolution shape is unchanged.
Results land on ResolvedAuditRow.detailCharacterNames, keyed by details
field name rather than by id, because access_list.holder_replaced carries two
character ids on one row and an id-keyed map could not say which field a name
came from.

src/app/admin/audit/summarize.ts — a characterRef builder that resolves
through a new characterNames map, falling back to the raw id. Nine
labelled(...) sites across eight actions switched to it, leading words
preserved.

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 every logAudit /
logAuditIfChanged call site across 14 service and job files) agreed on the
same nine keys:

Action Key(s)
account.created mainCharacterId
account.main_changed mainCharacterId
admin.main_changed mainCharacterId
account.merged characterId
admin.bootstrap_granted characterId
access_list.holder_designated characterId
access_list.holder_replaced characterId, previousCharacterId
token.subject_mismatch subjectCharacterId

Two keys are deliberately excluded, both of which a key-name heuristic would
have swept in: access_list.watch_*'s accessListId is an EVE access-list id
(access-lists.ts:130-135,166-171), and payout.item_repriced's itemId is a
loot_item id (payout-loot.ts:211-220).

Resolution is best-effort by design

Character rows are hard-deleted on unlink and reclaim
(accounts.ts:205, :505), and token.subject_mismatch names a token's EVE
subject 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 Kid while that character is linked and reverts to
main → 90000001 after it is unlinked. That matches what the Target column
already does, but note character.unlinked chose the opposite tradeoff and
snapshots name into details. 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 no
caller 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 through
String(). Number("0090012") is 90012, so a legacy or hand-inserted row
could 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:check all clean.
npm test → 92 files, 1498 tests passing.

Every new test was mutation-checked — shown to fail with only the code under
test removed:

  • summarize fallback → AssertionError: expected '—' to be 'main → ?'
  • round-trip guard → - {} + { "mainCharacterId": "Zero Padded" }

Where to focus review

DETAIL_CHARACTER_KEYS and the characterRef call sites are two
hand-maintained lists in different layers. All nine pairs agree today, but a
future characterRef added without a matching declaration would render the raw
id forever — indistinguishable from a legitimate hard-delete miss. A parity test
can't close this as things stand: Part is Render & { keys } and carries no
builder identity, so a characterRef part is indistinguishable from a
labelled one. Closing it needs a discriminator on Part, which felt like
scope this PR shouldn't take.

Separately, out of scope and unfixed: discord.role_changed writes a partial
key (discord-roles.ts:143, :369) that PARTS does not declare, so those
rows render an unexplained +1 more.

guarzo added 2 commits August 10, 2026 15:05
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
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 521afdd2-d3af-4ad6-b2a1-5b8d3c203fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 958120f and 5be9146.

📒 Files selected for processing (5)
  • src/app/admin/audit/page.tsx
  • src/app/admin/audit/summarize.ts
  • src/services/audit.ts
  • tests/audit-resolve.test.ts
  • tests/audit-summarize.test.ts

Comment @coderabbitai help to get the list of available commands.

@guarzo
guarzo enabled auto-merge (squash) August 10, 2026 19:23
@guarzo
guarzo merged commit f978dbd into main Aug 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant