Skip to content

feat(webapp): resolve which shard an environment mints run roots into - #4755

Draft
d-cs wants to merge 1 commit into
mainfrom
feature/mint-shard-selection-tri-13428
Draft

feat(webapp): resolve which shard an environment mints run roots into#4755
d-cs wants to merge 1 commit into
mainfrom
feature/mint-shard-selection-tri-13428

Conversation

@d-cs

@d-cs d-cs commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the shard-selection stage of run-id minting. resolveMintShard(env) returns which run-ops database an environment mints its new run roots into. Resolution order is the active shard list, then a per-environment or per-organization pin, then a rendezvous hash of the environment id.

Nothing changes for users on this merge. RUN_OPS_MINT_SHARDS is unset by default, so every environment resolves to the existing store and minting behaves exactly as it does today. No caller carries the returned key into an id yet.

Design

The existing gate that chooses between a cuid id and a run-ops id is untouched. The new stage runs after it, and the grace-window pattern is cloned into a separate module rather than widened, so the current flip behaviour keeps its semantics.

Placement uses rendezvous hashing, so adding a shard moves only about 1/(N+1) of environments and removing one moves only its own. Two details are load-bearing:

  • Scores are 64-bit sha256(envId \0 key). A 32-bit score collides at our environment count, and an undetected tie would resolve by iteration order.
  • The parsed key list is sorted. Without that, two deployments listing the same shards in a different CSV order would place environments differently.

Keys are validated at boot. A key outside [a-z0-9] cannot be stamped into an id, so it fails fast rather than throwing later on the mint path. new is accepted as a pin value, which holds one organization or environment on the current id format while the rest of the fleet moves.

A pin naming a shard that has left the active list falls through to the hash and reports once per environment. Honouring it would leak the drain the active list exists to perform, and throwing would fail triggers whenever a pinned shard drains.

Resolution adds no database queries to the trigger path: both pin levels live in the organization flag blob the mint call site already holds.

Notes for review

Determinism is a property of the pure core for fixed inputs. The wrapper supplies the clock, the same split effectiveMintKind already uses.

The active-set grace is stamped by RUN_OPS_MINT_SHARDS_PREV and RUN_OPS_MINT_SHARDS_FLIPPED_AT, both supplied by the operator alongside a change to RUN_OPS_MINT_SHARDS. Changing the list without them is un-graced and silent, which is an operational rule rather than something the code can enforce.

Two new feature-flag keys appear in the admin flag pages immediately. They are read only from the organization override blob, so they are locked on the global page.

The run-ops migration suites that need containers were not run locally.

Adds the third stage of the run-id mint gate chain. `resolveMintShard(env)`
returns the shard key an environment mints new roots into: the active shard
list, then a per-env or per-org pin, then a rendezvous hash of the environment
id. With `RUN_OPS_MINT_SHARDS` unset or empty it returns "new", which is
today's behaviour, so this merges inert.

`computeRunIdMintKind` and `mintFlipGrace.ts` are untouched. The grace pattern
is cloned into `mintShardGrace.ts` rather than widened, so the existing
cuid/runOpsId flip grace keeps its behaviour.

Design notes:

- Pure core plus env-bound wrapper, mirroring `runOpsMintKind.server.ts`.
  Determinism is a property of `computeMintShard` for fixed deps; the wrapper
  supplies the clock, exactly as `effectiveMintKind` takes `nowMs`.
- Zero new queries on the trigger hot path. Both pins live in the org override
  blob that `mintRunFriendlyId` already holds.
- HRW scores `sha256(envId \0 key)` at 64 bits, over a sorted key list, with a
  lexicographic tie-break. A 32-bit score collides at our environment count,
  and without the sort two deployments listing the same keys in a different CSV
  order would place environments differently.
- `parseShardCsv` rejects anything outside [a-z0-9] and rejects the reserved
  keys at boot. `generateRunOpsIdV2` throws on an out-of-alphabet char, so an
  unvalidated key would become a throw on the mint path.
- A pin outside the active set falls through to the hash and reports once per
  environment per process. Honouring it would leak the drain the active list
  performs; throwing would fail customer triggers whenever a pinned shard
  drains. The loud-on-unknown-key rule governs reading an id, not writing one.
- "new" is a legal pin value, holding one org or environment on gen-1 while the
  rest of the fleet mints gen-2. Without it, a non-empty active set moves every
  environment at once.
- The active-set grace is stamped by `RUN_OPS_MINT_SHARDS_PREV` and
  `RUN_OPS_MINT_SHARDS_FLIPPED_AT`. A prev list with no timestamp is dropped; a
  timestamp with an empty prev list graces a first activation.

No changeset and no `.server-changes` note: nothing user-visible, and no caller
carries the returned key into an id yet.
@changeset-bot

changeset-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: f2d9670

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds gen-2 mint shard configuration with boot-time validation and coordinated cutover metadata. Adds feature flags for organization and environment shard pins. Implements shard-set parsing, grace-period selection, deterministic rendezvous-hash assignment, generation-1 fallback, pin precedence, and rejected-pin logging. Adds tests for validation, cutover behavior, pin handling, assignment determinism, distribution, and shard-set changes.

Merge Risk: 🟡 Moderate · up to f2d96

The PR keeps default user behavior unchanged when shard configuration is unset, but its tests currently depend on unrelated production secrets and the shard cutover shares a grace setting with feature-flag flips, which can create inconsistent placement during deployment. Merge should wait for test isolation and explicit owner acceptance of the cutover-window design.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the design and behavior but omits the required issue, checklist, testing, changelog, and screenshots sections. Add the required template sections, complete the checklist, document testing results, and state when changelog or screenshots are not applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: resolving the shard used for environment run-root minting.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mint-shard-selection-tri-13428

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.

@d-cs d-cs self-assigned this Aug 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/webapp/app/v3/featureFlags.ts (1)

95-120: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Reuse isValidPinValue instead of duplicating the pin contract.

mintShardGrace.ts already exports SHARD_KEY_PATTERN, GEN_1_PIN_VALUE, and isValidPinValue. This file now re-implements that predicate twice: once at Line 100 and once at Line 116. The alphabet regex /^[a-z0-9]$/ and the "new" literal exist in three places across the two files.

The write-side validator and the read-side resolver must agree. If the alphabet or the gen-1 sentinel changes in mintShardGrace.ts, these copies keep accepting a pin that readPin in runOpsMintShard.server.ts then discards, which silently un-pins an environment.

mintShardGrace.ts imports only a type from @trigger.dev/core, so importing it here adds no runtime cycle.

♻️ Proposed refactor to share one predicate

Add the import at the top of the file:

 import { z } from "zod";
+import { isValidPinValue } from "./runOpsMigration/mintShardGrace";

Then reuse it in both schemas:

-  [FEATURE_FLAG.runOpsMintShard]: z
-    .string()
-    .refine((v) => v === "new" || /^[a-z0-9]$/.test(v), 'must be a single [a-z0-9] char, or "new"'),
+  [FEATURE_FLAG.runOpsMintShard]: z
+    .string()
+    .refine(isValidPinValue, 'must be a single [a-z0-9] char, or "new"'),
   // Per-environment pins as JSON: {"<environmentId>": "<shard key>"}. A JSON string because
   // this catalog is scalar-only. Rejected at write, so a typo cannot silently un-pin an env.
   [FEATURE_FLAG.runOpsMintShardEnvPins]: z.string().superRefine((raw, ctx) => {
     const fail = (message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, message });
 
     let parsed: unknown;
     try {
       parsed = JSON.parse(raw);
     } catch {
       return fail("must be valid JSON");
     }
     if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
       return fail("must be a JSON object mapping environment id to shard key");
     }
     for (const [environmentId, value] of Object.entries(parsed)) {
-      if (typeof value !== "string" || !(value === "new" || /^[a-z0-9]$/.test(value))) {
+      if (!isValidPinValue(value)) {
         fail(`"${environmentId}" must map to a single [a-z0-9] char, or "new"`);
       }
     }
   }),
apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts (1)

119-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for mintShardStampWarning.

mintShardGrace.ts exports mintShardStampWarning, and this file tests every other export. The warning is the only operator signal for a half-configured cutover, where RUN_OPS_MINT_SHARDS_PREV is set but RUN_OPS_MINT_SHARDS_FLIPPED_AT is not. It has three branches and none are covered.

💚 Proposed tests
+describe("mintShardStampWarning", () => {
+  it("stays quiet while the active set is empty", () => {
+    expect(
+      mintShardStampWarning({ shards: "", prev: "a", flippedAt: undefined })
+    ).toBeUndefined();
+  });
+
+  it("warns when prev is set but the flip timestamp is not", () => {
+    expect(mintShardStampWarning({ shards: "a", prev: "b", flippedAt: undefined })).toMatch(
+      /FLIPPED_AT/
+    );
+  });
+
+  it("stays quiet when both halves of the stamp are set", () => {
+    expect(
+      mintShardStampWarning({ shards: "a", prev: "b", flippedAt: new Date(T).toISOString() })
+    ).toBeUndefined();
+  });
+
+  it("stays quiet when prev is empty", () => {
+    expect(mintShardStampWarning({ shards: "a", prev: "", flippedAt: undefined })).toBeUndefined();
+  });
+});

Add mintShardStampWarning to the import list at Line 3.

apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts (1)

162-168: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Give the shard-set cutover its own grace env var.

Line 165 reuses RUN_OPS_MINT_FLIP_GRACE_MS as graceMs. env.server.ts documents that variable as the grace for a runOpsMintKind flip, and requires it to exceed RUN_OPS_MINT_FLAG_CACHE_TTL_MS plus the control-plane cache TTL. The two windows cover different things:

  • RUN_OPS_MINT_FLIP_GRACE_MS absorbs feature-flag cache staleness across processes.
  • The shard-set window absorbs deploy skew, because RUN_OPS_MINT_SHARDS is a deploy-time value and a rolling deploy runs old and new CSVs at the same time.

The sizing inputs differ, so one knob cannot serve both. An operator who retunes the flag-cache grace also retunes the shard cutover window without knowing it. If that window becomes shorter than the rolling-deploy duration, pods mint into different shard sets at the same instant, which is the exact condition _PREV and _FLIPPED_AT exist to prevent.

The new RUN_OPS_MINT_SHARDS_* block already owns _PREV and _FLIPPED_AT. Add the grace there while no caller consumes the returned shard key yet.

♻️ Proposed change

In apps/webapp/app/env.server.ts, next to the other shard variables:

     RUN_OPS_MINT_SHARDS: shardCsvString(),
     RUN_OPS_MINT_SHARDS_PREV: shardCsvString(),
     RUN_OPS_MINT_SHARDS_FLIPPED_AT: z.string().datetime().optional(),
+    // Cutover window for a RUN_OPS_MINT_SHARDS set change. Must exceed the rolling-deploy
+    // duration so every process crosses the boundary together. Sized independently of
+    // RUN_OPS_MINT_FLIP_GRACE_MS, which absorbs feature-flag cache staleness instead.
+    RUN_OPS_MINT_SHARDS_GRACE_MS: z.coerce.number().int().default(90_000),

Then in this file:

   return computeMintShard(environment, {
     resolution: shardResolution,
     nowMs: Date.now(),
-    graceMs: env.RUN_OPS_MINT_FLIP_GRACE_MS,
+    graceMs: env.RUN_OPS_MINT_SHARDS_GRACE_MS,
     orgFeatureFlags: environment.orgFeatureFlags,
     onPinRejected: reportPinRejected,
   });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 858e4dcd-c0d9-438b-a74a-848f9e0b870f

📥 Commits

Reviewing files that changed from the base of the PR and between dd3a1c0 and f2d9670.

📒 Files selected for processing (6)
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (32)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: obsmap / 🧪 Unit Tests: Observability Map
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: code-quality / code-quality
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Prefer static imports over dynamic imports. Only use dynamic import() when:

  • Circular dependencies cannot be resolved otherwise
  • Code splitting is genuinely needed for performance
  • The module must be loaded conditionally at runtime

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

**/*.{test,spec}.{ts,tsx}: We use vitest exclusively. Never mock anything - use testcontainers instead.
Test files go next to source files (e.g., MyService.ts -> MyService.test.ts).

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: Access environment variables through the env export of env.server.ts instead of directly accessing process.env
Use subpath exports from @trigger.dev/core package instead of importing from the root @trigger.dev/core path

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Do not import env.server.ts directly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/app/**/*.{ts,tsx}: For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.
Use useCallback and useMemo only for context provider values, expensive derived data used as a dependency, or stable references required by dependency arrays; do not wrap ordinary event handlers or trivial computations.
Use named constants for sentinel or placeholder values instead of scattering raw string literals across comparisons.

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
apps/webapp/app/**/*.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/app/**/*.ts: Never use request.signal to detect client disconnects. Use getRequestAbortSignal() from app/services/httpAsyncStorage.server.ts, which is wired to Express response close events.
Access environment variables through the env export from app/env.server.ts; never use process.env directly.
Always use Prisma findFirst instead of findUnique.
Always use the $transaction helper from ~/db.server, never call prisma.$transaction or $replica.$transaction directly. Pass isolation levels as strings, use Serializable for correctness-critical read-then-write invariants, and guard possibly undefined helper results when a definite value is required.

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
apps/webapp/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Test files must not import app/env.server.ts; pass configuration as options instead.

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
apps/webapp/app/v3/**/*.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

New code must target Run Engine V2 through the singleton in app/v3/runEngine.server.ts; do not reintroduce V1 execution paths. V1 branches may only reject or finalize gracefully with a clean 4xx.

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Add crumbs as you write code — not just when debugging. Mark lines with
// @Crumbs or wrap blocks in `// `#region` `@crumbs. They stay on the
branch throughout development and are stripped by agentcrumbs strip
before merge.
Do not invent new namespaces — pick from this table or ask first.
Do not flag // @Crumbs markers, `#region `@crumbs blocks, or agentcrumbs
imports in reviews. These are temporary debug instrumentation stripped
before merge. Data logged in crumbs (IDs, names, values) never reaches
production.

Files:

  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts
  • apps/webapp/app/v3/featureFlags.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts
  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
🧠 Learnings (1)
📚 Learning: 2026-06-04T18:16:35.386Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3836
File: apps/supervisor/src/backpressure/backpressureMonitor.ts:3-5
Timestamp: 2026-06-04T18:16:35.386Z
Learning: When reviewing TypeScript in this repo, apply the rule “prefer type aliases over interfaces” only to data/object shapes and union/intersection type modeling. If an interface is being used as a behavioral contract for collaborators to implement (e.g., method-shape interfaces that define required behavior, such as `BackpressureLogger` / `BackpressureSignalSource` in `apps/supervisor/src/backpressure/backpressureMonitor.ts`), keep it as an `interface` and do not flag it as a type-alias-vs-interface violation.

Applied to files:

  • apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts
  • apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts
🔇 Additional comments (8)
apps/webapp/app/v3/runOpsMigration/mintShardGrace.ts (2)

6-20: LGTM!

Also applies to: 29-48, 53-62, 66-79, 83-95


22-25: 🗄️ Data Integrity & Integration

No ShardKey type mismatch

ShardKey is "legacy" | "new" | string, so it admits "new" and all single [a-z0-9] characters. The type predicate is sound for these values.

			> Likely an incorrect or invalid review comment.
apps/webapp/app/env.server.ts (1)

7-7: LGTM!

Also applies to: 45-60, 2019-2026

apps/webapp/app/v3/featureFlags.ts (2)

29-31: LGTM!

Also applies to: 133-141


98-120: 🩺 Stability & Availability

No resolver change is needed. ZodEffects uses the resolver’s { type: "string" } fallback, so both admin UIs render StringControl. The global page intentionally renders these flags as locked through GLOBAL_LOCKED_FLAGS.

			> Likely an incorrect or invalid review comment.
apps/webapp/app/v3/runOpsMigration/mintShardGrace.test.ts (1)

1-118: LGTM!

apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.ts (1)

1-13: LGTM!

Also applies to: 15-26, 31-44, 48-57, 63-65, 67-81, 90-112, 116-132, 136-146, 148-161

apps/webapp/app/v3/runOpsMigration/runOpsMintShard.server.test.ts (1)

5-41: LGTM!

Also applies to: 43-58, 60-80, 82-185, 187-248

Comment on lines +1 to +3
import { describe, expect, it } from "vitest";
import { computeMintShard, type MintShardDeps } from "./runOpsMintShard.server";
import { type MintShardSetResolution } from "./mintShardGrace";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

This test imports env.server.ts indirectly. Move the pure core into its own module.

Line 2 imports ./runOpsMintShard.server. That module's Line 3 is import { env } from "~/env.server". Loading this test therefore evaluates env.server.ts, which runs EnvironmentSchema.parse(process.env) at its Line 2500 and requires DATABASE_URL, DIRECT_URL, SESSION_SECRET, MAGIC_LINK_SECRET, a 32-byte ENCRYPTION_KEY, MANAGED_WORKER_SECRET, DEPLOY_REGISTRY_HOST, and CLICKHOUSE_URL. It also executes the module-level side effects at runOpsMintShard.server.ts Lines 116-132, including a logger.warn call.

The test then either fails to load without a complete environment, or passes only because of ambient environment values.

runOpsMintShard.server.ts Line 83 already documents computeMintShard as "PURE CORE — no env, no clock, no I/O; tests drive this directly". Extract that pure core into a module that does not import env.server, and keep only the env-bound wrapper in runOpsMintShard.server.ts.

As per path instructions: "Do not import env.server.ts directly or indirectly into test files; instead pass environment-dependent values through options/parameters" and "Test files must not import app/env.server.ts; pass configuration as options instead."

♻️ Proposed split

Move MintShardDeps, asRecord, readEnvPin, readPin, shardScore, hrwSelect, and computeMintShard into a new mintShardAssignment.ts that imports only node:crypto, the ShardKey type, ~/v3/featureFlags, and ./mintShardGrace.

Then in runOpsMintShard.server.ts:

-import { createHash } from "node:crypto";
 import type { ShardKey } from "`@trigger.dev/core/v3/isomorphic`";
 import { env } from "~/env.server";
 import { logger } from "~/services/logger.server";
-import { FEATURE_FLAG } from "~/v3/featureFlags";
 import {
   buildMintShardResolution,
-  effectiveMintShardSet,
-  GEN_1_PIN_VALUE,
-  isValidPinValue,
   mintShardStampWarning,
   type MintShardSetResolution,
 } from "./mintShardGrace";
+import { computeMintShard, type MintShardDeps } from "./mintShardAssignment";

And in this test file:

-import { computeMintShard, type MintShardDeps } from "./runOpsMintShard.server";
+import { computeMintShard, type MintShardDeps } from "./mintShardAssignment";

Rename this file to mintShardAssignment.test.ts so it sits next to its source.

Source: Path instructions

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