Skip to content

fix(prompt-filter): clarify risk labels and usage totals - #488

Merged
james-6-23 merged 4 commits into
james-6-23:mainfrom
ifThink404:codex/prompt-policy-unified-pr
Aug 8, 2026
Merged

fix(prompt-filter): clarify risk labels and usage totals#488
james-6-23 merged 4 commits into
james-6-23:mainfrom
ifThink404:codex/prompt-policy-unified-pr

Conversation

@ifThink404

@ifThink404 ifThink404 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • reconcile API Key account/group usage totals across time ranges
  • preserve per-key review key management and deleted-account attribution
  • show readable Chinese moderation risk labels while retaining upstream identifiers

Verification

  • Go targeted tests passed
  • frontend typecheck and production build passed
  • production release and health checks passed on v2.7.2-fr-20260807.2

The production operational cutover script no longer blocks releases on external review-key availability; that change is maintained on the production host separately from the application PR.

Summary by CodeRabbit

  • New Features

    • Added management for saved prompt-review API keys, including masked identifiers, listing, selective deletion, and deletion safeguards.
    • Added localized moderation category labels in English and Chinese.
    • Added API-key usage reconciliation details for grouped, ungrouped, duplicate, deleted, and historically missing accounts.
  • Improvements

    • Prompt-review test results now identify the associated saved key without exposing secrets.
    • Usage dashboards explain billing and account-count reconciliation, with clearer indicators for ungrouped accounts.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds prompt-review API-key listing and deletion with stable masked identifiers. It also adds API-key account-usage reconciliation metrics for grouped, ungrouped, duplicate, and multi-group accounts, with corresponding frontend displays and localization.

Changes

Prompt-review API-key management

Layer / File(s) Summary
Key identity and response contracts
admin/prompt_filter.go, admin/prompt_filter_test.go, frontend/src/types.ts
Review-key results and descriptors now include stable identifiers, masked values, indexes, and list metadata. Tests verify identifier stability and secret redaction.
Key listing and deletion flow
admin/handler.go, admin/prompt_filter.go, database/prompt_filter_review_keys.go, database/*test.go, admin/prompt_filter_test.go
Admin routes list and delete review keys. Deletion validates identifiers, prevents removing the final enabled key, persists changes with compare-and-swap, and updates runtime settings.
Review-key management UI
frontend/src/api.ts, frontend/src/pages/PromptFilter.tsx, frontend/src/locales/*.json
The PromptFilter page loads masked keys, confirms deletion, refreshes state, displays test results, and uses localized labels and feedback.

API-key account-usage reconciliation

Layer / File(s) Summary
Usage reconciliation aggregation
admin/handler.go, admin/api_key_account_usage_test.go, frontend/src/types.ts
Account usage responses now include grouped, ungrouped, duplicate, unique-grouped, and multi-group reconciliation metrics while preserving de-duplicated summaries.
Usage reconciliation display
frontend/src/components/APIKeyTokenUsagePanel.tsx, frontend/src/locales/*.json
The frontend stores and displays reconciliation totals, includes ungrouped accounts in summaries, and marks deleted or historically unmatched groups.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: james-6-23, ekti123456, huangye123

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the risk-label and API-key usage-total changes, which are major parts of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
admin/api_key_account_usage_test.go (1)

41-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover AccountBilled reconciliation.

The test validates reconciliation only for UserBilled. The aggregation also returns AccountBilled, and the panel displays it. Add grouped, ungrouped, duplicate, and reconciled AccountBilled assertions.

Proposed test additions
- if reconciliation.GroupedTotal.Accounts != 3 || reconciliation.GroupedTotal.Requests != 8 || reconciliation.GroupedTotal.TotalTokens != 500 || math.Abs(reconciliation.GroupedTotal.UserBilled-1.5) > 1e-9 {
+ if reconciliation.GroupedTotal.Accounts != 3 || reconciliation.GroupedTotal.Requests != 8 || reconciliation.GroupedTotal.TotalTokens != 500 || math.Abs(reconciliation.GroupedTotal.AccountBilled-1.0) > 1e-9 || math.Abs(reconciliation.GroupedTotal.UserBilled-1.5) > 1e-9 {
- if reconciliation.Ungrouped.Accounts != 1 || reconciliation.Ungrouped.TotalTokens != 50 || math.Abs(reconciliation.Ungrouped.UserBilled-0.15) > 1e-9 {
+ if reconciliation.Ungrouped.Accounts != 1 || reconciliation.Ungrouped.Requests != 1 || reconciliation.Ungrouped.TotalTokens != 50 || math.Abs(reconciliation.Ungrouped.AccountBilled-0.1) > 1e-9 || math.Abs(reconciliation.Ungrouped.UserBilled-0.15) > 1e-9 {
- if reconciliation.Duplicate.Accounts != 1 || reconciliation.Duplicate.Requests != 3 || reconciliation.Duplicate.TotalTokens != 200 || math.Abs(reconciliation.Duplicate.UserBilled-0.6) > 1e-9 {
+ if reconciliation.Duplicate.Accounts != 1 || reconciliation.Duplicate.Requests != 3 || reconciliation.Duplicate.TotalTokens != 200 || math.Abs(reconciliation.Duplicate.AccountBilled-0.4) > 1e-9 || math.Abs(reconciliation.Duplicate.UserBilled-0.6) > 1e-9 {
+ reconciledAccountBilled := reconciliation.GroupedTotal.AccountBilled + reconciliation.Ungrouped.AccountBilled - reconciliation.Duplicate.AccountBilled
+ if math.Abs(reconciledAccountBilled-summary.AccountBilled) > 1e-9 {
+   t.Fatalf("reconciled account billed = %f, summary = %f", reconciledAccountBilled, summary.AccountBilled)
+ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/api_key_account_usage_test.go` around lines 41 - 52, Extend the
reconciliation assertions in the test to validate AccountBilled for
reconciliation.GroupedTotal, reconciliation.Ungrouped, and
reconciliation.Duplicate, using the same floating-point tolerance as UserBilled.
Add a reconciled AccountBilled calculation and compare it with
summary.AccountBilled, preserving the existing UserBilled checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin/prompt_filter.go`:
- Around line 113-122: Update maskPromptReviewAPIKey so keys of length 5 through
7 return the fully masked value instead of exposing overlapping prefix and
suffix characters. Preserve the existing masking behavior for longer keys and
add tests covering each length from 5 through 7.
- Around line 711-713: Update the handler around
SetPromptFilterConfigWithAdvancedRaw so a runtime configuration failure after
the compare-and-swap restores the previously persisted prompt-filter key state
before returning the error. Prefer rolling back the persisted change using the
prior configuration, or make both updates atomic, and add a test covering this
failure path.

In `@frontend/src/pages/PromptFilter.tsx`:
- Around line 2898-2909: Update the key-deletion flow around
deletePromptReviewAPIKey so the prompt_filter_review_api_key draft is cleared or
reconciled after deleting keyID, preventing promptFilterSavePayload from
submitting the deleted key on a later save. Preserve the existing
configured-key, form-count, and review-test result updates.

---

Nitpick comments:
In `@admin/api_key_account_usage_test.go`:
- Around line 41-52: Extend the reconciliation assertions in the test to
validate AccountBilled for reconciliation.GroupedTotal,
reconciliation.Ungrouped, and reconciliation.Duplicate, using the same
floating-point tolerance as UserBilled. Add a reconciled AccountBilled
calculation and compare it with summary.AccountBilled, preserving the existing
UserBilled checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18b46bd9-b390-426d-8d01-742861cb5efd

📥 Commits

Reviewing files that changed from the base of the PR and between 571a81e and 435f716.

📒 Files selected for processing (12)
  • admin/api_key_account_usage_test.go
  • admin/handler.go
  • admin/prompt_filter.go
  • admin/prompt_filter_test.go
  • database/prompt_filter_review_keys.go
  • database/prompt_filter_review_keys_test.go
  • frontend/src/api.ts
  • frontend/src/components/APIKeyTokenUsagePanel.tsx
  • frontend/src/locales/en.json
  • frontend/src/locales/zh.json
  • frontend/src/pages/PromptFilter.tsx
  • frontend/src/types.ts

Comment thread admin/prompt_filter.go
Comment on lines +113 to +122
func maskPromptReviewAPIKey(key string) string {
key = strings.TrimSpace(key)
if len(key) <= 4 {
return "••••"
}
prefix := ""
if len(key) >= 3 {
prefix = key[:3]
}
return prefix + "••••" + key[len(key)-4:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Prevent short key disclosure.

For keys with 5 to 7 characters, the three-character prefix and four-character suffix overlap. The response then exposes every character of the key.

Return a fully masked value when the visible prefix and suffix would overlap. Add tests for keys with lengths 5 through 7.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/prompt_filter.go` around lines 113 - 122, Update maskPromptReviewAPIKey
so keys of length 5 through 7 return the fully masked value instead of exposing
overlapping prefix and suffix characters. Preserve the existing masking behavior
for longer keys and add tests covering each length from 5 through 7.

Comment thread admin/prompt_filter.go
Comment on lines +711 to +713
if err := h.store.SetPromptFilterConfigWithAdvancedRaw(runtimeCfg, h.store.GetPromptFilterAdvancedConfig()); err != nil {
writeError(c, http.StatusInternalServerError, "审查 Key 已保存,但运行时配置更新失败")
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Keep persisted and runtime key state consistent.

If SetPromptFilterConfigWithAdvancedRaw fails after the compare-and-swap succeeds, the database no longer contains the key but the running service continues to use it. This is critical when the deletion revokes a compromised credential.

Roll back the compare-and-swap before returning an error, or make the persistence and runtime update one atomic operation. Add a failure-path test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/prompt_filter.go` around lines 711 - 713, Update the handler around
SetPromptFilterConfigWithAdvancedRaw so a runtime configuration failure after
the compare-and-swap restores the previously persisted prompt-filter key state
before returning the error. Prefer rolling back the persisted change using the
prior configuration, or make both updates atomic, and add a test covering this
failure path.

Comment on lines +2898 to +2909
const result = await api.deletePromptReviewAPIKey(keyID)
setConfiguredReviewKeys(result.items)
setForm((current) => ({
...current,
prompt_filter_review_api_key_configured: result.count > 0,
prompt_filter_review_api_key_count: result.count,
}))
setReviewTestResult((current) => current ? {
...current,
key_count: result.count,
results: current.results?.filter((item) => item.key_id !== keyID),
} : null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent a stale key draft from restoring a deleted key.

If an operator edits prompt_filter_review_api_key, deletes one saved key, and then saves the form, the unchanged draft can write the deleted key back. promptFilterSavePayload includes this field whenever it is non-empty.

After deletion, clear or reconcile the draft before a later save. Alternatively, block deletion until the operator saves or discards the key draft.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/PromptFilter.tsx` around lines 2898 - 2909, Update the
key-deletion flow around deletePromptReviewAPIKey so the
prompt_filter_review_api_key draft is cleared or reconciled after deleting
keyID, preventing promptFilterSavePayload from submitting the deleted key on a
later save. Preserve the existing configured-key, form-count, and review-test
result updates.

@james-6-23
james-6-23 merged commit 24f8b00 into james-6-23:main Aug 8, 2026
6 checks passed
james-6-23 added a commit that referenced this pull request Aug 8, 2026
- Preserve the review key column on full-settings saves that did not
  change it (PreservePromptFilterReviewAPIKey, mirroring the
  custom-patterns guard), so a stale snapshot from another instance can
  no longer resurrect a deleted key.
- Compare the review key CAS against the raw stored value: SQL TRIM only
  strips spaces while strings.TrimSpace also strips newlines/tabs, so a
  padded legacy value made deletion fail with a permanent 409.
- Mask review keys on rune boundaries and only reveal prefix/suffix for
  keys of 12+ characters; short keys were fully reconstructible from the
  overlapping fragments and multi-byte keys were sliced mid-character.
- Cover the new DELETE route in IsSensitiveEndpoint and log successful
  deletions (masked key, client IP, remaining count).
- Bump the api-key-accounts cache namespace to v2 so pre-deploy Redis
  entries without the reconciliation field are not served as zeros.
- Add the new review-key and moderation-category strings to zh-TW and
  fall back to the raw category name for unmapped moderation labels.
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.

2 participants