Skip to content

feat(frontend): isolate public trust and add adjudication#445

Open
ShivamB25 wants to merge 14 commits intojunhoyeo:mainfrom
ShivamB25:split/public-trust-adjudication
Open

feat(frontend): isolate public trust and add adjudication#445
ShivamB25 wants to merge 14 commits intojunhoyeo:mainfrom
ShivamB25:split/public-trust-adjudication

Conversation

@ShivamB25
Copy link
Copy Markdown

@ShivamB25 ShivamB25 commented Apr 18, 2026

Summary

  • isolate public competitive views to trusted data only
  • keep leaderboard, profile, embed, badge, and landing surfaces in sync after trust changes
  • add suspicious submission review and adjudication with rank-aware cache refresh

Changes

  • preserve trusted global ranks across all-time, period, search, and standalone user-rank reads
  • refresh leaderboard page and landing surfaces after trusted submit and delete flows
  • separate non-competitive review metadata from public competitive profile data
  • add review list, detail, and adjudication APIs for suspicious submissions
  • refresh affected users' cached rank-bearing surfaces after adjudication changes the leaderboard order
  • close the list API cache invalidation gap for /api/leaderboard

Testing

  • focused reader, cache, and adjudication Vitest coverage
  • frontend typecheck
  • frontend build
  • live API and runtime verification on the local app

Depends on #444
Refs #441

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 18, 2026

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

Project Deployment Actions Updated (UTC)
tokscale Ready Ready Preview, Comment Apr 18, 2026 9:18pm

Request Review

@ShivamB25
Copy link
Copy Markdown
Author

This PR is intentionally stacked.

If it were marked as a standalone ready-to-merge change, reviewers would be looking at a diff that conceptually depends on code still under review in the earlier PR(s). Reviewing it in order keeps the trust-boundary changes, replay semantics, and public-surface changes easier to reason about.

@ShivamB25 ShivamB25 marked this pull request as ready for review April 18, 2026 21:36
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

7 issues found across 45 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/frontend/src/lib/leaderboard/getLeaderboard.ts">

<violation number="1" location="packages/frontend/src/lib/leaderboard/getLeaderboard.ts:354">
P1: All-time leaderboard and user-rank now rank the full verified-user set in memory on each cache miss instead of using DB-side pagination/ranking.</violation>
</file>

<file name="packages/frontend/src/lib/submissionReviews.ts">

<violation number="1" location="packages/frontend/src/lib/submissionReviews.ts:192">
P2: List endpoint returns detail-shaped review artifacts (including payload/audit/submissionHash) even though it is typed and intended as a summary list, exposing unnecessary data and bloating responses.</violation>
</file>

<file name="packages/frontend/src/lib/submissionPersistence.ts">

<violation number="1" location="packages/frontend/src/lib/submissionPersistence.ts:163">
P1: Check-then-insert on `submissions.userId` is race-prone; concurrent trusted submits can hit a unique-violation instead of reusing the same submission row.</violation>

<violation number="2" location="packages/frontend/src/lib/submissionPersistence.ts:329">
P3: Unsorted Set materialization makes persisted client/model arrays nondeterministic across identical submissions.</violation>
</file>

<file name="packages/frontend/src/lib/validation/submissionTrust.ts">

<violation number="1" location="packages/frontend/src/lib/validation/submissionTrust.ts:33">
P1: Unvalidated `timestampMs` can throw inside `toISOString()`, crashing trust assessment for schema-valid submissions.</violation>
</file>

<file name="packages/frontend/src/app/u/[username]/ProfilePageClient.tsx">

<violation number="1" location="packages/frontend/src/app/u/[username]/ProfilePageClient.tsx:235">
P2: Locale/timezone-dependent `toLocaleString()` can cause server/client hydration mismatches and unstable displayed timestamps.</violation>
</file>

<file name="packages/frontend/__tests__/api/settingsSubmittedDataDelete.test.ts">

<violation number="1" location="packages/frontend/__tests__/api/settingsSubmittedDataDelete.test.ts:145">
P3: Use different mocked row counts for submissions vs. reviews so the test can catch a mix-up in which delete result is used to compute `deletedSubmissions`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -1,7 +1,8 @@
import { unstable_cache } from "next/cache";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P1: All-time leaderboard and user-rank now rank the full verified-user set in memory on each cache miss instead of using DB-side pagination/ranking.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/lib/leaderboard/getLeaderboard.ts, line 354:

<comment>All-time leaderboard and user-rank now rank the full verified-user set in memory on each cache miss instead of using DB-side pagination/ranking.</comment>

<file context>
@@ -397,59 +307,65 @@ async function fetchLeaderboardData(
 
-  const totalUsers = Number(globalStats[0]?.uniqueUsers) || 0;
-  const totalPages = Math.ceil(totalUsers / limit);
+  const rankedUsers = rankLeaderboardUsers(allTimeUsers, sortBy);
+  const filteredUsers = rankedUsers.filter((user) =>
+    matchesLeaderboardSearch(user, search)
</file context>
Fix with Cubic

},
};

const [existingSubmission] = await tx
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P1: Check-then-insert on submissions.userId is race-prone; concurrent trusted submits can hit a unique-violation instead of reusing the same submission row.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/lib/submissionPersistence.ts, line 163:

<comment>Check-then-insert on `submissions.userId` is race-prone; concurrent trusted submits can hit a unique-violation instead of reusing the same submission row.</comment>

<file context>
@@ -0,0 +1,353 @@
+    },
+  };
+
+  const [existingSubmission] = await tx
+    .select({ id: submissions.id })
+    .from(submissions)
</file context>
Fix with Cubic

const TRUSTED_RETROACTIVE_WINDOW_DAYS = 30;

function getUtcDateStringFromTimestamp(timestampMs: number): string {
return new Date(timestampMs).toISOString().slice(0, 10);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P1: Unvalidated timestampMs can throw inside toISOString(), crashing trust assessment for schema-valid submissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/lib/validation/submissionTrust.ts, line 33:

<comment>Unvalidated `timestampMs` can throw inside `toISOString()`, crashing trust assessment for schema-valid submissions.</comment>

<file context>
@@ -0,0 +1,154 @@
+const TRUSTED_RETROACTIVE_WINDOW_DAYS = 30;
+
+function getUtcDateStringFromTimestamp(timestampMs: number): string {
+  return new Date(timestampMs).toISOString().slice(0, 10);
+}
+
</file context>
Fix with Cubic

: await baseQuery.orderBy(desc(submissionReviews.updatedAt));

return rows.map((row) => {
const artifact = mapSubmissionReviewArtifact(row);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P2: List endpoint returns detail-shaped review artifacts (including payload/audit/submissionHash) even though it is typed and intended as a summary list, exposing unnecessary data and bloating responses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/lib/submissionReviews.ts, line 192:

<comment>List endpoint returns detail-shaped review artifacts (including payload/audit/submissionHash) even though it is typed and intended as a summary list, exposing unnecessary data and bloating responses.</comment>

<file context>
@@ -0,0 +1,365 @@
+    : await baseQuery.orderBy(desc(submissionReviews.updatedAt));
+
+  return rows.map((row) => {
+    const artifact = mapSubmissionReviewArtifact(row);
+    return artifact;
+  });
</file context>
Fix with Cubic

<ReviewMetaList>
<ReviewMetaItem>
<ReviewMetaLabel>Updated</ReviewMetaLabel>
<ReviewMetaValue>{new Date(data.nonCompetitive.updatedAt).toLocaleString()}</ReviewMetaValue>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P2: Locale/timezone-dependent toLocaleString() can cause server/client hydration mismatches and unstable displayed timestamps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/app/u/[username]/ProfilePageClient.tsx, line 235:

<comment>Locale/timezone-dependent `toLocaleString()` can cause server/client hydration mismatches and unstable displayed timestamps.</comment>

<file context>
@@ -164,6 +190,80 @@ const EARLY_ADOPTERS = ["code-yeongyu", "gtg7784", "qodot"];
+              <ReviewMetaList>
+                <ReviewMetaItem>
+                  <ReviewMetaLabel>Updated</ReviewMetaLabel>
+                  <ReviewMetaValue>{new Date(data.nonCompetitive.updatedAt).toLocaleString()}</ReviewMetaValue>
+                </ReviewMetaItem>
+                {data.nonCompetitive.reasonCodes.length > 0 ? (
</file context>
Suggested change
<ReviewMetaValue>{new Date(data.nonCompetitive.updatedAt).toLocaleString()}</ReviewMetaValue>
<ReviewMetaValue>
{new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short",
timeZone: "UTC",
}).format(new Date(data.nonCompetitive.updatedAt))}
</ReviewMetaValue>
Fix with Cubic

@@ -0,0 +1,353 @@
import { eq, inArray, sql } from "drizzle-orm";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P3: Unsorted Set materialization makes persisted client/model arrays nondeterministic across identical submissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/src/lib/submissionPersistence.ts, line 329:

<comment>Unsorted Set materialization makes persisted client/model arrays nondeterministic across identical submissions.</comment>

<file context>
@@ -0,0 +1,353 @@
+      reasoningTokens: totalReasoning,
+      dateStart: aggregates.dateStart,
+      dateEnd: aggregates.dateEnd,
+      sourcesUsed: Array.from(allClients),
+      modelsUsed: Array.from(allModels),
+      cliVersion: data.meta.version,
</file context>
Fix with Cubic

});
mockState.setDeletedRows([{ id: "submission-1" }]);
mockState.setDeletedSubmissionRows([{ id: "submission-1" }]);
mockState.setDeletedReviewRows([{ id: "review-1" }]);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 18, 2026

Choose a reason for hiding this comment

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

P3: Use different mocked row counts for submissions vs. reviews so the test can catch a mix-up in which delete result is used to compute deletedSubmissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/__tests__/api/settingsSubmittedDataDelete.test.ts, line 145:

<comment>Use different mocked row counts for submissions vs. reviews so the test can catch a mix-up in which delete result is used to compute `deletedSubmissions`.</comment>

<file context>
@@ -125,7 +141,8 @@ describe("DELETE /api/settings/submitted-data", () => {
     });
-    mockState.setDeletedRows([{ id: "submission-1" }]);
+    mockState.setDeletedSubmissionRows([{ id: "submission-1" }]);
+    mockState.setDeletedReviewRows([{ id: "review-1" }]);
 
     const response = await DELETE(createRequest());
</file context>
Suggested change
mockState.setDeletedReviewRows([{ id: "review-1" }]);
mockState.setDeletedReviewRows([{ id: "review-1-1" }, { id: "review-1-2" }]);
Fix with Cubic

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