Skip to content

fix(admin): harden Sentry/Vercel observability and error-identity attribution - #777

Merged
njrini99-code merged 1 commit into
mainfrom
fix/admin-observability-sentry-vercel-hardening
Jul 3, 2026
Merged

fix(admin): harden Sentry/Vercel observability and error-identity attribution#777
njrini99-code merged 1 commit into
mainfrom
fix/admin-observability-sentry-vercel-hardening

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Summary

Bucket D/E of the HelmV3 stabilization brief (the admin-rollup 42501 root-cause gating fix from Bucket C was already shipped separately in #775, discovered while building the Supabase drift guard).

  • TriageQueue wording — an app-origin incident with events but zero known user_id/user_email now shows "unknown user" instead of "0 users." The raw count only means "no event carried an identity" (anonymous/system failure, or identity wasn't wired into the observed-action call) — not that nobody was affected. Sentry-origin items keep the literal count since Sentry's userCount is a real zero-means-zero metric.
  • Error identity attributionwithAdminObserved now accepts an optional contextFrom(args) callback so a caller that already knows its subject (e.g. generateRoundRecap always knows roundId, even though the wrapper's own resolveObservedUser() only ever sees the authenticated user) can attribute admin_events rows to the right round/player/team instead of relying on auth identity alone. Wired into generateRoundRecap as the concrete example named in the brief. Enrichment failures are swallowed — they can never mask the real error or block logging.
  • Vercel web-insights false-zero fixfetchVercelWebInsights previously mapped an HTTP 401/403 from the web-insights endpoint to { visitors: 0 } wrapped in status: 'ok' — indistinguishable from genuinely quiet traffic. Now surfaces status: 'error' like fetchVercelDeployments already does. /admin/deploys already treats any non-'ok' status as PanelNoData, so no page change was needed — verified by reading the consumer, not assumed.
  • New docsdocs/operations/SENTRY_ADMIN_READ_API.md (required SENTRY_READ_TOKEN scopes, why not to reuse the CI sourcemap token, fail-soft state table) and docs/operations/VERCEL_ADMIN_DEPLOYS_RUNBOOK.md (required env vars, failure-mode table, links to the CI preview-pending runbook).

Verified, not changed

Sentry's read API (sentry-api.ts) and the Vercel deploys page were already fail-soft (unconfigured/error envelopes, never throw) before this PR — confirmed by reading the code, documented in the new runbooks rather than re-implemented.

Test plan

  • npm run typecheck — clean.
  • npm run lint — clean (--max-warnings 0).
  • npm run test:run — 429 files / 4405 passed, 39 skipped (was 428/4398; +1 file/+7 tests, matching what's added/modified).
  • New/updated tests: observed-action.test.ts (contextFrom derivation, omission, and failure-safety), vercel-api.test.ts (403 now surfaces status: 'error' instead of fake zeros — this test previously asserted the bug being fixed), affected-users-label.test.ts (new — wording logic in isolation).

Related

Made with Cursor

…ribution

Bucket D/E of the HelmV3 stabilization brief (admin-rollup root-cause
gating was already fixed separately in the Supabase drift PR).

- TriageQueue: an `app`-origin incident with events but zero known
  user_id/user_email now shows "unknown user" instead of "0 users" —
  the raw count only means "no event carried an identity" (anonymous/
  system failure, or identity wasn't wired into the observed-action
  call), not that nobody was affected. Sentry-origin items keep the
  literal count since Sentry's userCount is a real zero-means-zero
  metric.
- observed-action.ts: withAdminObserved now accepts an optional
  contextFrom(args) callback so a caller that already knows its
  subject (e.g. generateRoundRecap always knows roundId, even though
  the wrapper's own resolveObservedUser() only ever sees the
  *authenticated* user) can attribute admin_events rows to the right
  round/player/team instead of relying on auth identity alone. Wired
  into generateRoundRecap as the concrete example from the brief.
  Enrichment failures are swallowed — they can never mask the real
  error or block logging.
- vercel-api.ts: fetchVercelWebInsights previously mapped an HTTP
  401/403 from the web-insights endpoint to `{ visitors: 0 }` wrapped
  in status:'ok' — indistinguishable from genuinely quiet traffic.
  Now surfaces status:'error' like fetchVercelDeployments already
  does; /admin/deploys already treats any non-'ok' status as
  PanelNoData, so no page change was needed.
- New docs: SENTRY_ADMIN_READ_API.md (required SENTRY_READ_TOKEN
  scopes, why not to reuse the CI sourcemap token, fail-soft state
  table) and VERCEL_ADMIN_DEPLOYS_RUNBOOK.md (required env vars,
  failure-mode table, links to the CI preview-pending runbook).

Sentry read API (sentry-api.ts) and the Vercel deploys page were
already fail-soft (unconfigured/error envelopes, never throw) —
verified, not changed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@supabase

supabase Bot commented Jul 3, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Jul 3, 2026 9:40pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds two operations docs (Sentry admin read API, Vercel deploys runbook), extends withAdminObserved with a contextFrom callback for richer error logging, makes fetchVercelWebInsights return a failure status instead of zero-filled data on HTTP errors, and adds an affectedUsersLabel helper to TriageQueue.

Changes

Admin observability, error-handling, and operations docs

Layer / File(s) Summary
withAdminObserved context enrichment
src/lib/admin/observed-action.ts, src/lib/admin/__tests__/observed-action.test.ts, src/app/golf/actions/round-recap.ts
Adds ObservedActionContext interface and opts.contextFrom callback so errors are logged with roundId/playerId/teamId/route; errors thrown by contextFrom are swallowed; round-recap wiring now passes roundId via contextFrom; tests cover derivation, default nulls, and throw-safety.
Vercel web insights failure surfacing
src/lib/admin/vercel-api.ts, src/lib/admin/__tests__/vercel-api.test.ts
fetchVercelWebInsights tracks the first non-OK HTTP status across concurrent period fetches and returns a failed result instead of zero-filled visitor counts; test updated to expect error status with HTTP code included.
Triage queue affected-users label
src/app/admin/_components/TriageQueue.tsx, src/app/admin/_components/__tests__/affected-users-label.test.ts
Adds exported affectedUsersLabel returning "unknown user" for app-origin items with zero affectedUsers but nonzero occurrences, otherwise pluralized counts; subtitle rendering now uses this helper; new test suite added.
Sentry and Vercel admin operations docs
docs/operations/SENTRY_ADMIN_READ_API.md, docs/operations/VERCEL_ADMIN_DEPLOYS_RUNBOOK.md
New docs detailing required env vars, token scopes, fallback token behavior, and failure-mode-to-UI-state mappings for /admin/errors and /admin/deploys, with cross-references between the two.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • njrini99-code/helmv3#727: Extends the same withAdminObserved/observed-action instrumentation framework used to wrap golf/coach actions.

Suggested labels: security


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Auth Check In Server Actions ❌ Error src/app/golf/actions/round-recap.ts:160 exports an async server action, but the implementation reaches .from() at 91/104/127 with no supabase.auth.getUser() anywhere in the file. Add an auth probe before the first DB query in this action path (or in the shared Supabase helper), and keep .from()/rpc() calls gated on that result.
Title check ⚠️ Warning The title is relevant, but it uses the disallowed scope "admin" instead of a required Conventional Commits scope. Change the scope to an allowed one such as docs or ops, e.g. "docs: harden Sentry/Vercel observability and error-identity attribution".
Conventional Commits ❓ Inconclusive HEAD subject matches the convention, but the PR title isn’t available in repo metadata, so both required strings can’t be verified. Provide the PR title or PR metadata; I can only confirm the squashed subject from HEAD, not the title.
✅ Passed checks (9 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the PR changes: admin observability, runbooks, and Vercel error handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
No Service-Role In Client Bundles ✅ Passed No changed file outside admin paths references SUPABASE_SERVICE_ROLE_KEY; the new createClient() calls use '@/lib/supabase/server' with NEXT_PUBLIC_SUPABASE_ANON_KEY, not the service role key.
Rls Coverage On New Tables ✅ Passed PASS: merge-base..HEAD changes only docs and src files; no migration files were modified, so the RLS CREATE TABLE/policy rule is not triggered.
Sport-Prefixed Table Names ✅ Passed Only touched Supabase queries are golf_* tables in round-recap; no bare coaches/players/teams/rounds/events tables appear in changed TS/TSX code.
No Destructive Writes ✅ Passed No changed save/submit/sync code does DELETE+INSERT rebuilds; the only delete calls are in-memory Set.delete in TriageQueue, and the only DB write is an update in round-recap.
No Edits To Historical Migrations ✅ Passed PASS: HEAD^..HEAD changes docs/src only; git diff --name-only HEAD^ HEAD -- supabase/migrations/** was empty, so no historical migrations were edited.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/admin-observability-sentry-vercel-hardening
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Harden admin observability: correct Sentry/Vercel fail-soft + richer error attribution

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Fix misleading affected-user copy for app-origin incidents with missing identity attribution.
• Allow server actions to enrich admin error logs with subject context derived from call args.
• Treat Vercel web-insights auth/HTTP failures as errors (not fake zero traffic) and document ops
 setup.
Diagram

graph TD
  A["/admin/errors + /admin/deploys"] --> B["TriageQueue"] --> C{"affectedUsersLabel"}
  D["withAdminObserved"] --> E["logServerException"] --> F[("Supabase: admin_events")]
  G["vercel-api.fetchVercelWebInsights"] --> H{{"Vercel Web Insights API"}}

  subgraph Legend
    direction LR
    _ui["UI/Module"] ~~~ _fn{"Decision/Helper"} ~~~ _db[("Database")] ~~~ _ext{{"External API"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Pass explicit context at invocation time (not via contextFrom)
  • ➕ Avoids re-deriving context from args and makes attribution explicit per call site
  • ➕ Enables dynamic context that may depend on intermediate computation, not just initial args
  • ➖ Requires changing the wrapper call signature and all call sites (wider churn)
  • ➖ Easier for callers to forget to pass context; the current opts-based approach is simpler
2. AsyncLocalStorage request context enrichment
  • ➕ Can automatically capture route/request identifiers and other ambient context without threading args
  • ➕ Scales well if many server actions need consistent enrichment fields
  • ➖ More moving parts and harder debugging; risk of context leaks across async boundaries
  • ➖ Overkill for a small, bounded set of subject fields (roundId/playerId/teamId)

Recommendation: The PR’s approach (optional opts.contextFrom(args) with swallow-on-error semantics) is the best tradeoff for now: minimal API surface change, keeps the wrapper generic, and ensures enrichment can’t mask the primary error. If subject attribution expands across many actions/routes, revisit an AsyncLocalStorage-based solution for broader, consistent request/route capture.

Files changed (9) +292 / -8

Enhancement (2) +50 / -2
round-recap.tsEnrich generateRoundRecap observed errors with roundId context +10/-1

Enrich generateRoundRecap observed errors with roundId context

• Wires the new withAdminObserved contextFrom callback to attribute failures to the known roundId rather than relying solely on authenticated-user identity.

src/app/golf/actions/round-recap.ts

observed-action.tsAdd optional contextFrom(args) to enrich admin_events attribution +40/-1

Add optional contextFrom(args) to enrich admin_events attribution

• Defines ObservedActionContext and extends withAdminObserved options with an optional contextFrom callback. On error, derives extra context from original args in a try/catch and logs roundId/playerId/teamId/route as nullable fields without risking masking the primary failure.

src/lib/admin/observed-action.ts

Bug fix (2) +31 / -2
TriageQueue.tsxShow 'unknown user' when app incidents have events but no identity +18/-1

Show 'unknown user' when app incidents have events but no identity

• Introduces affectedUsersLabel() to avoid implying 'nobody was affected' when app-origin incidents have occurrences but zero known user identities. Updates the TriageQueue row copy to use the helper while preserving Sentry-origin semantics.

src/app/admin/_components/TriageQueue.tsx

vercel-api.tsTreat non-OK web-insights HTTP responses as errors (not '0 visitors') +13/-1

Treat non-OK web-insights HTTP responses as errors (not '0 visitors')

• Tracks the first non-ok HTTP status across period requests and returns a failed() AdminFetchResult when encountered. Preserves fail-soft behavior while eliminating the false-zero observability bug for expired/misscoped tokens.

src/lib/admin/vercel-api.ts

Tests (3) +72 / -4
affected-users-label.test.tsAdd unit tests for affectedUsersLabel wording rules +27/-0

Add unit tests for affectedUsersLabel wording rules

• Covers the app-origin unknown-identity case, real counts, Sentry-origin zero behavior, and a defensive zero-occurrence edge case.

src/app/admin/_components/tests/affected-users-label.test.ts

observed-action.test.tsTest contextFrom enrichment, omission defaults, and safety on callback throw +37/-0

Test contextFrom enrichment, omission defaults, and safety on callback throw

• Adds coverage ensuring contextFrom derives subject fields from original args at error time, null defaults are preserved when omitted, and that contextFrom exceptions never mask the real error or prevent logging.

src/lib/admin/tests/observed-action.test.ts

vercel-api.test.tsUpdate web-insights test to expect error envelope on 403/404 +8/-4

Update web-insights test to expect error envelope on 403/404

• Replaces the prior assertion that mapped HTTP failures to ok/zero visitors, and now asserts status:'error', an error message containing the HTTP status, and null data.

src/lib/admin/tests/vercel-api.test.ts

Documentation (2) +139 / -0
SENTRY_ADMIN_READ_API.mdAdd Sentry admin read-token runbook and failure-state mapping +77/-0

Add Sentry admin read-token runbook and failure-state mapping

• Documents required SENTRY_READ_TOKEN scopes and why CI sourcemap tokens are insufficient. Explains fail-soft behavior and how unconfigured/error states appear in the admin UI.

docs/operations/SENTRY_ADMIN_READ_API.md

VERCEL_ADMIN_DEPLOYS_RUNBOOK.mdAdd Vercel deploys/web-insights env var + failure-mode runbook +62/-0

Add Vercel deploys/web-insights env var + failure-mode runbook

• Documents required Vercel env vars, explains admin-page rendering for ok/unconfigured/error envelopes, and calls out the prior web-insights false-zero failure mode. Links to the CI preview pending-status runbook.

docs/operations/VERCEL_ADMIN_DEPLOYS_RUNBOOK.md

@coderabbitai coderabbitai Bot added the security Auth, secrets, RLS, PII, webhooks label Jul 3, 2026
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (3) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 97 rules

Grey Divider


Action required

1. src/app/admin not in registry 📘 Rule violation ⚙ Maintainability
Description
This PR changes Helm Bridge admin functionality under src/app/admin/**, but memory/registry.yml
has no mapping covering src/app/admin/**. This violates the requirement to update the feature
registry when touching unmapped feature areas.
Code

src/app/admin/_components/TriageQueue.tsx[R22-37]

+/**
+ * "0 users" reads as "this affected nobody," which is misleading for `app`
+ * incidents: affectedUsers there is a count of DISTINCT KNOWN identities
+ * (user_id/user_email), so 0 usually means the failure happened before/
+ * outside auth (anonymous, system/cron, or identity wasn't wired into the
+ * observed-action call) — not that zero people were impacted. Sentry-origin
+ * items use Sentry's own userCount, which IS a real zero-means-zero metric,
+ * so only `app` incidents get the "unknown" wording.
+ */
+export function affectedUsersLabel(item: Pick<TriageItem, 'origin' | 'affectedUsers' | 'occurrences'>): string {
+  if (item.origin === 'app' && item.affectedUsers === 0 && item.occurrences > 0) {
+    return 'unknown user';
+  }
+  const n = item.affectedUsers;
+  return `${n} user${n === 1 ? '' : 's'}`;
+}
Relevance

⭐⭐⭐ High

Team has accepted updating memory/registry.yml for touched feature areas (PR #296, #517).

PR-#296
PR-#517

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519254 requires that modified feature areas are present (or explicitly gap-marked)
in memory/registry.yml. The PR modifies code in src/app/admin/**, and the registry shows an
admin_platform feature but does not map any src/app/admin/** paths.

Rule 1519254: Update feature registry mappings when touching unmapped features
src/app/admin/_components/TriageQueue.tsx[1-14]
src/app/admin/_components/TriageQueue.tsx[22-37]
memory/registry.yml[955-1010]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Modified code under `src/app/admin/**` is not represented in `memory/registry.yml` feature mappings.

## Issue Context
This PR changes `src/app/admin/_components/TriageQueue.tsx` (Helm Bridge admin), but `memory/registry.yml` does not include any `code.routes/components/...` glob that matches `src/app/admin/**`.

## Fix Focus Areas
- memory/registry.yml[955-1010]
- src/app/admin/_components/TriageQueue.tsx[1-14]
- src/app/admin/_components/TriageQueue.tsx[22-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. admin-platform doc not updated 📘 Rule violation ⚙ Maintainability
Description
This PR changes admin triage/user-impact labeling and admin observability failure behavior, but no
corresponding memory/features/* documentation update was made. This can cause the feature’s
current-state documentation to drift from actual admin behavior.
Code

src/app/admin/_components/TriageQueue.tsx[R22-37]

+/**
+ * "0 users" reads as "this affected nobody," which is misleading for `app`
+ * incidents: affectedUsers there is a count of DISTINCT KNOWN identities
+ * (user_id/user_email), so 0 usually means the failure happened before/
+ * outside auth (anonymous, system/cron, or identity wasn't wired into the
+ * observed-action call) — not that zero people were impacted. Sentry-origin
+ * items use Sentry's own userCount, which IS a real zero-means-zero metric,
+ * so only `app` incidents get the "unknown" wording.
+ */
+export function affectedUsersLabel(item: Pick<TriageItem, 'origin' | 'affectedUsers' | 'occurrences'>): string {
+  if (item.origin === 'app' && item.affectedUsers === 0 && item.occurrences > 0) {
+    return 'unknown user';
+  }
+  const n = item.affectedUsers;
+  return `${n} user${n === 1 ? '' : 's'}`;
+}
Relevance

⭐⭐ Medium

No consistent history enforcing memory/features/* doc updates for behavior changes; only
registry+docs ask was partially accepted.

PR-#296

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519257 requires updating relevant memory/features/* docs (or adding an explicit
justification) when business behavior changes. The PR changes admin-facing triage labeling and admin
deploys data error semantics, but memory/features/admin-platform.md does not reflect these changes
in this branch.

Rule 1519257: Update memory feature docs when changing business behavior
src/app/admin/_components/TriageQueue.tsx[22-37]
src/lib/admin/vercel-api.ts[109-149]
memory/features/admin-platform.md[1-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Business/UX behavior changed in admin observability/triage, but `memory/features/*` docs were not updated to reflect the new behavior.

## Issue Context
The triage queue copy now renders `unknown user` for certain `app`-origin incidents instead of `0 users`, and Vercel web-insights now surfaces auth failures as `status: 'error'` instead of returning fake zeros.

## Fix Focus Areas
- memory/features/admin-platform.md[1-78]
- src/app/admin/_components/TriageQueue.tsx[22-37]
- src/lib/admin/vercel-api.ts[109-149]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Coverage scanner brittle 🐞 Bug ☼ Reliability
Description
The static action coverage scanner’s WRAP_RE assumes the withAdminObserved opts object contains no
nested braces, but the new contextFrom usage introduces nested {} via `([roundId]) => ({ roundId
})`. This can truncate the captured optsSrc and make wrapper coverage/feature extraction tests
fragile (and potentially miss regressions if opts ordering changes).
Code

src/app/golf/actions/round-recap.ts[R150-156]

+    // The round detail page already knows the round it's rendering — wire
+    // that into admin_events on failure instead of relying solely on the
+    // authenticated user id (which is null for any unauthenticated/expired
+    // session edge case, and never carries which round/player was involved
+    // either way).
+    contextFrom: ([roundId]) => ({ roundId }),
+  },
Relevance

⭐⭐ Medium

No historical evidence found about scanner regex brittleness; referenced coverage-scanner file not
found in repo history.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new withAdminObserved call site includes a nested object literal inside the opts object, while
the scanner explicitly documents and implements a regex that stops at the first } in opts; this
mismatch makes the scan logic fragile.

src/app/golf/actions/round-recap.ts[145-158]
src/lib/admin/tests/coverage-scanner.ts[35-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scanActionFile()` in `src/lib/admin/__tests__/coverage-scanner.ts` uses a regex capture `({[^}]*})` for the withAdminObserved opts object and explicitly relies on “non-nested object literal” formatting. The PR introduces a nested brace sequence inside opts (`contextFrom: ([roundId]) => ({ roundId })`), which violates that assumption and can lead to truncated option capture and brittle coverage checks.

## Issue Context
This is test/tooling code, but it enforces the wrapper-coverage contract across many server action files; fragility here can reduce confidence in the observability coverage guarantees.

## Fix Focus Areas
- src/lib/admin/__tests__/coverage-scanner.ts[33-55]
- src/app/golf/actions/round-recap.ts[145-158]

## What to change
- Replace `WRAP_RE`’s `\{[^}]*\}` capture with a small balanced-brace extractor:
 - Regex-match up to the start of the opts object (`withAdminObserved(..., {`), then scan forward counting `{`/`}` (skipping over strings/comments as needed) to find the matching closing brace.
 - Alternatively, switch to a lightweight TS/JS parser (e.g., TypeScript compiler API) since this is test-only.
- Add/adjust a unit test fixture demonstrating `contextFrom: (...) => ({ ... })` so the scanner behavior is locked in.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. round-recap.ts server action misplaced 📘 Rule violation ⌂ Architecture
Description
src/app/golf/actions/round-recap.ts is a server action module ('use server') but it is not
located under src/app/actions/ as required. This breaks the required server-action placement
convention and can lead to inconsistent action discovery/organization.
Code

src/app/golf/actions/round-recap.ts[R147-156]

+  {
+    sport: 'golf',
+    feature: 'round_review_ai',
+    // The round detail page already knows the round it's rendering — wire
+    // that into admin_events on failure instead of relying solely on the
+    // authenticated user id (which is null for any unauthenticated/expired
+    // session edge case, and never carries which round/player was involved
+    // either way).
+    contextFrom: ([roundId]) => ({ roundId }),
+  },
Relevance

⭐ Low

Repo commonly keeps actions under src/app/<sport>/actions (e.g., PR #296), not centralized
src/app/actions.

PR-#296

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519234 requires server action modules (identified via 'use server') to live
under src/app/actions/. The modified module src/app/golf/actions/round-recap.ts starts with
'use server' but is not in that directory.

Rule 1519234: Place Next.js server actions in src/app/actions directory
src/app/golf/actions/round-recap.ts[1-2]
src/app/golf/actions/round-recap.ts[145-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR modifies a server action file located outside the required `src/app/actions/` directory.

## Issue Context
The file `src/app/golf/actions/round-recap.ts` contains a top-level `'use server'` directive, indicating it is a server action module, but it lives under `src/app/golf/actions/`.

## Fix Focus Areas
- src/app/golf/actions/round-recap.ts[1-2]
- src/app/golf/actions/round-recap.ts[145-165]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +22 to +37
/**
* "0 users" reads as "this affected nobody," which is misleading for `app`
* incidents: affectedUsers there is a count of DISTINCT KNOWN identities
* (user_id/user_email), so 0 usually means the failure happened before/
* outside auth (anonymous, system/cron, or identity wasn't wired into the
* observed-action call) — not that zero people were impacted. Sentry-origin
* items use Sentry's own userCount, which IS a real zero-means-zero metric,
* so only `app` incidents get the "unknown" wording.
*/
export function affectedUsersLabel(item: Pick<TriageItem, 'origin' | 'affectedUsers' | 'occurrences'>): string {
if (item.origin === 'app' && item.affectedUsers === 0 && item.occurrences > 0) {
return 'unknown user';
}
const n = item.affectedUsers;
return `${n} user${n === 1 ? '' : 's'}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. src/app/admin not in registry 📘 Rule violation ⚙ Maintainability

This PR changes Helm Bridge admin functionality under src/app/admin/**, but memory/registry.yml
has no mapping covering src/app/admin/**. This violates the requirement to update the feature
registry when touching unmapped feature areas.
Agent Prompt
## Issue description
Modified code under `src/app/admin/**` is not represented in `memory/registry.yml` feature mappings.

## Issue Context
This PR changes `src/app/admin/_components/TriageQueue.tsx` (Helm Bridge admin), but `memory/registry.yml` does not include any `code.routes/components/...` glob that matches `src/app/admin/**`.

## Fix Focus Areas
- memory/registry.yml[955-1010]
- src/app/admin/_components/TriageQueue.tsx[1-14]
- src/app/admin/_components/TriageQueue.tsx[22-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +22 to +37
/**
* "0 users" reads as "this affected nobody," which is misleading for `app`
* incidents: affectedUsers there is a count of DISTINCT KNOWN identities
* (user_id/user_email), so 0 usually means the failure happened before/
* outside auth (anonymous, system/cron, or identity wasn't wired into the
* observed-action call) — not that zero people were impacted. Sentry-origin
* items use Sentry's own userCount, which IS a real zero-means-zero metric,
* so only `app` incidents get the "unknown" wording.
*/
export function affectedUsersLabel(item: Pick<TriageItem, 'origin' | 'affectedUsers' | 'occurrences'>): string {
if (item.origin === 'app' && item.affectedUsers === 0 && item.occurrences > 0) {
return 'unknown user';
}
const n = item.affectedUsers;
return `${n} user${n === 1 ? '' : 's'}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. admin-platform doc not updated 📘 Rule violation ⚙ Maintainability

This PR changes admin triage/user-impact labeling and admin observability failure behavior, but no
corresponding memory/features/* documentation update was made. This can cause the feature’s
current-state documentation to drift from actual admin behavior.
Agent Prompt
## Issue description
Business/UX behavior changed in admin observability/triage, but `memory/features/*` docs were not updated to reflect the new behavior.

## Issue Context
The triage queue copy now renders `unknown user` for certain `app`-origin incidents instead of `0 users`, and Vercel web-insights now surfaces auth failures as `status: 'error'` instead of returning fake zeros.

## Fix Focus Areas
- memory/features/admin-platform.md[1-78]
- src/app/admin/_components/TriageQueue.tsx[22-37]
- src/lib/admin/vercel-api.ts[109-149]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +150 to +156
// The round detail page already knows the round it's rendering — wire
// that into admin_events on failure instead of relying solely on the
// authenticated user id (which is null for any unauthenticated/expired
// session edge case, and never carries which round/player was involved
// either way).
contextFrom: ([roundId]) => ({ roundId }),
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Coverage scanner brittle 🐞 Bug ☼ Reliability

The static action coverage scanner’s WRAP_RE assumes the withAdminObserved opts object contains no
nested braces, but the new contextFrom usage introduces nested {} via `([roundId]) => ({ roundId
})`. This can truncate the captured optsSrc and make wrapper coverage/feature extraction tests
fragile (and potentially miss regressions if opts ordering changes).
Agent Prompt
## Issue description
`scanActionFile()` in `src/lib/admin/__tests__/coverage-scanner.ts` uses a regex capture `({[^}]*})` for the withAdminObserved opts object and explicitly relies on “non-nested object literal” formatting. The PR introduces a nested brace sequence inside opts (`contextFrom: ([roundId]) => ({ roundId })`), which violates that assumption and can lead to truncated option capture and brittle coverage checks.

## Issue Context
This is test/tooling code, but it enforces the wrapper-coverage contract across many server action files; fragility here can reduce confidence in the observability coverage guarantees.

## Fix Focus Areas
- src/lib/admin/__tests__/coverage-scanner.ts[33-55]
- src/app/golf/actions/round-recap.ts[145-158]

## What to change
- Replace `WRAP_RE`’s `\{[^}]*\}` capture with a small balanced-brace extractor:
  - Regex-match up to the start of the opts object (`withAdminObserved(..., {`), then scan forward counting `{`/`}` (skipping over strings/comments as needed) to find the matching closing brace.
  - Alternatively, switch to a lightweight TS/JS parser (e.g., TypeScript compiler API) since this is test-only.
- Add/adjust a unit test fixture demonstrating `contextFrom: (...) => ({ ... })` so the scanner behavior is locked in.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@njrini99-code
njrini99-code merged commit bf70a00 into main Jul 3, 2026
39 of 40 checks passed
@njrini99-code
njrini99-code deleted the fix/admin-observability-sentry-vercel-hardening branch July 3, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant