Skip to content

refactor(workflow-processing): add atomic claim/lock mechanism and flatten helpers structure#23

Merged
larryro merged 5 commits into
mainfrom
enhance-workflow-processing_records
Dec 18, 2025
Merged

refactor(workflow-processing): add atomic claim/lock mechanism and flatten helpers structure#23
larryro merged 5 commits into
mainfrom
enhance-workflow-processing_records

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Dec 18, 2025

Summary

This PR improves the workflow processing system by adding atomic claim/lock mechanisms to prevent concurrent processing race conditions and refactors the directory structure for better organization.

Changes

🔒 Atomic Claim/Lock Mechanism

  • Convert find operations from queries to mutations for atomic claiming
  • Add status field to workflowProcessingRecords schema (in_progress | completed)
  • Rename findUnprocessedWithCustomQuery to findAndClaimUnprocessed
  • Add recordClaimed function for explicit claim operations
  • Simplify return types to single document instead of arrays (find-one semantics)
  • Update all callers to use ctx.runMutation instead of ctx.runQuery

This prevents race conditions where multiple concurrent workflow executions could pick up and process the same entity simultaneously.

�� Directory Structure Refactoring

  • Flatten helper files from model/workflow_processing_records/helpers/ to model/workflow_processing_records/
  • Extract calculateCutoffTimestamp helper to centralize cutoff logic
  • Add constants module with BACKOFF_NEVER_REPROCESS constant

⚙️ LLM Configuration

  • Increase default maxSteps from 10 to 20 for LLM tool calling
  • Update product_recommendation_email workflow maxSteps to allow multiple steps for tool calls

Summary by CodeRabbit

  • New Features

    • Added atomic record claiming mechanism to prevent duplicate processing of workflow records.
    • Introduced "never reprocess" backoff configuration for records that should be processed once only.
    • Added processing state tracking ('in_progress' and 'completed' statuses) for workflow records.
  • Refactor

    • Consolidated record processing utilities for more reliable single-record handling.
    • Enhanced email generator workflow to support multi-step LLM orchestration.

✏️ Tip: You can customize this high-level summary in your review settings.

…vent concurrent processing

- Convert find operations from queries to mutations for atomic claiming
- Add 'status' field to workflowProcessingRecords schema ('in_progress' | 'completed')
- Rename findUnprocessedWithCustomQuery to findAndClaimUnprocessed
- Add recordClaimed function for explicit claim operations
- Simplify return types to single document instead of arrays (find-one semantics)
- Update all callers to use ctx.runMutation instead of ctx.runQuery
- Remove limit parameter since operations now always return at most one document

This prevents race conditions where multiple concurrent workflow executions
could pick up and process the same entity simultaneously.
…lation

- Add BACKOFF_NEVER_REPROCESS constant (-1) to prevent reprocessing
- Extract calculateCutoffTimestamp helper to centralize cutoff logic
- When backoffHours is -1, uses epoch to ensure one-time processing
- Update findUnprocessed, findProductRecommendationByStatus, and
  findUnprocessedOpenConversation to use the new helper
- Export new constants and helper from module index
…M maxSteps

- Move helper files from model/workflow_processing_records/helpers/ to
  model/workflow_processing_records/ (flattening directory structure)
- Add calculate_cutoff_timestamp and constants modules
- Increase default maxSteps from 10 to 20 for LLM tool calling
- Update product_recommendation_email workflow maxSteps from 1 to 20
  to allow multiple steps for tool calls + final response
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 18, 2025

📝 Walkthrough

Walkthrough

The PR refactors the workflow processing records system to implement atomic record claiming. It introduces new utility modules for calculating timestamp cutoffs and claiming records, converts existing query-based operations to mutations that acquire locks during retrieval, removes the generic findUnprocessedWithCustomQuery helper in favor of a specialized findAndClaimUnprocessed function, and restructures result shapes from arrays-with-counts to single-object-or-null returns. A new optional status field is added to the workflowProcessingRecords schema to track processing states ('in_progress' or 'completed').

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • find_and_claim_unprocessed.ts — Core logic for atomic retrieval and claiming; requires validation of the resume-point fallback mechanism and claiming atomicity.
  • record_claimed.ts — Implements the claiming mutation; verify insert/patch logic and field assignments (status: 'in_progress').
  • Type system refactoring — Renamed interfaces and restructured result shapes from { documents: T[], count: number } to { document: T | null }; trace usage across all consuming files.
  • Context type changes — Multiple functions converted from QueryCtx to MutationCtx; verify all callsites are updated accordingly.
  • Import path updates — Several relative path changes (e.g., ../../../_generated/server../../_generated/server); confirm all paths resolve correctly.

Possibly related PRs


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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/platform/convex/model/workflow_processing_records/record_processed.ts (1)

32-64: Fix inconsistent indentation.

Multiple lines in this function have tab-based indentation while the rest of the file uses spaces.

🔎 Suggested fix:
-	  // Check if this record has already been recorded for this workflow
+  // Check if this record has already been recorded for this workflow
   const existing = await ctx.db
     .query('workflowProcessingRecords')
     ...
     .first();

-	  const now = Date.now();
-	  if (existing) {
-	    // Transition existing record to completed state
-	    await ctx.db.patch(existing._id, {
-	      processedAt: now,
-	      status: 'completed',
-	      metadata,
-	    });
-	    return existing._id;
-	  }
+  const now = Date.now();
+  if (existing) {
+    // Transition existing record to completed state
+    await ctx.db.patch(existing._id, {
+      processedAt: now,
+      status: 'completed',
+      metadata,
+    });
+    return existing._id;
+  }

-	  // Create new record in completed state (backwards-compatible path when no claim exists)
-	  return await ctx.db.insert('workflowProcessingRecords', {
-	    organizationId,
-	    tableName,
-	    recordId,
-	    wfDefinitionId,
-	    recordCreationTime,
-	    processedAt: now,
-	    status: 'completed',
-	    metadata,
-	  });
+  // Create new record in completed state (backwards-compatible path when no claim exists)
+  return await ctx.db.insert('workflowProcessingRecords', {
+    organizationId,
+    tableName,
+    recordId,
+    wfDefinitionId,
+    recordCreationTime,
+    processedAt: now,
+    status: 'completed',
+    metadata,
+  });
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between a0d0243 and a5704d9.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (24)
  • services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/constants.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/find_unprocessed.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts (0 hunks)
  • services/platform/convex/model/workflow_processing_records/index.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/record_claimed.ts (1 hunks)
  • services/platform/convex/model/workflow_processing_records/record_processed.ts (2 hunks)
  • services/platform/convex/model/workflow_processing_records/run_query.ts (3 hunks)
  • services/platform/convex/model/workflow_processing_records/types.ts (2 hunks)
  • services/platform/convex/predefined_workflows/product_recommendation_email.ts (1 hunks)
  • services/platform/convex/schema.ts (1 hunks)
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts (2 hunks)
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts (2 hunks)
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts (2 hunks)
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts (1 hunks)
  • services/platform/convex/workflow/helpers/nodes/llm/utils/validate_and_normalize_config.ts (1 hunks)
  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts (1 hunks)
  • services/platform/convex/workflow_processing_records.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*

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

Use English only for ALL user-facing content including UI components, labels, buttons, dialogs, forms, toast messages, error messages, success messages, comments, documentation, README files, variable names, function names, and type names

Files:

  • services/platform/convex/workflow/helpers/nodes/llm/utils/validate_and_normalize_config.ts
  • services/platform/convex/model/workflow_processing_records/record_claimed.ts
  • services/platform/convex/model/workflow_processing_records/constants.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts
  • services/platform/convex/schema.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts
  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/model/workflow_processing_records/index.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/record_processed.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
**/*.{ts,tsx,js,jsx}

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

**/*.{ts,tsx,js,jsx}: Use Vercel AI SDK with OpenAI - import from 'ai' and '@ai-sdk/openai', never use raw OpenAI SDK or OpenRouter
Never hallucinate API keys - always use environment variables and existing .env configuration
Use camelCase for function names (e.g., getUserData)
Use SCREAMING_SNAKE_CASE for constants (e.g., API_BASE_URL, MAX_RETRIES)
Use feature flags with enums (TypeScript) or const objects (JavaScript) with UPPERCASE_WITH_UNDERSCORE naming
Implement error handling with try-catch pattern: check for result.error and display descriptive toast messages using result.error as title

Files:

  • services/platform/convex/workflow/helpers/nodes/llm/utils/validate_and_normalize_config.ts
  • services/platform/convex/model/workflow_processing_records/record_claimed.ts
  • services/platform/convex/model/workflow_processing_records/constants.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts
  • services/platform/convex/schema.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts
  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/model/workflow_processing_records/index.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/record_processed.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use kebab-case for file names (e.g., user-profile.tsx)
Use PascalCase for component names (e.g., UserProfile)
Use descriptive messages as toast title (never generic 'Error'), with optional description for additional context only
Follow component structure: 'use client' directive, imports, interface Props, hooks, effects, event handlers, then render
Prioritize data fetching methods in order: Server Actions (preferred), Route Handlers (when needed), Client-side (minimal use)
Use React.memo for expensive components to optimize performance
Use Next.js Image component for all images instead of native img tags
Use dynamic imports for code splitting

Files:

  • services/platform/convex/workflow/helpers/nodes/llm/utils/validate_and_normalize_config.ts
  • services/platform/convex/model/workflow_processing_records/record_claimed.ts
  • services/platform/convex/model/workflow_processing_records/constants.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts
  • services/platform/convex/schema.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts
  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/model/workflow_processing_records/index.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/record_processed.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
services/*/convex/*.ts

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

Thin wrapper API modules (like services/platform/convex/documents.ts) may export multiple Convex functions as wrappers that delegate to model helpers, but must not contain business logic and must only perform argument/return validation and delegation

Files:

  • services/platform/convex/schema.ts
  • services/platform/convex/workflow_processing_records.ts
🧠 Learnings (36)
📚 Learning: 2025-12-15T14:01:55.275Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/workflow/actions/conversation/helpers/update_conversations.ts:7-10
Timestamp: 2025-12-15T14:01:55.275Z
Learning: In Convex action helpers (services/platform/convex/workflow/actions/**/helpers/*.ts), using Record<string, unknown> for update parameters is acceptable when field validation is handled at the mutation level in Convex. This provides flexibility for dynamic field updates while keeping validation centralized at the mutation layer.

Applied to files:

  • services/platform/convex/model/workflow_processing_records/record_claimed.ts
  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts
  • services/platform/convex/schema.ts
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts
  • services/platform/convex/model/workflow_processing_records/index.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/record_processed.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-12-15T14:01:50.330Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/workflow/actions/conversation/helpers/update_conversations.ts:7-10
Timestamp: 2025-12-15T14:01:50.330Z
Learning: In Convex action helper files under services/platform/convex/workflow/actions/**/helpers/*.ts, prefer using Record<string, unknown> for update parameter types since field validation is performed at the mutation layer. This allows dynamic update shapes while keeping validation centralized at mutation runtime.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Try to use as few calls from actions to queries and mutations as possible to avoid race conditions, since queries and mutations are transactions

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Always use the new Convex function syntax with `query`, `mutation`, `internalQuery`, `internalMutation`, `action`, or `internalAction` with explicit `args`, `returns`, and `handler` properties

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Try to use as few calls from actions to queries and mutations as possible, as they are transactions and splitting logic introduces race condition risks.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/model/workflow_processing_records/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Use ctx.runQuery to call queries, ctx.runMutation for mutations, and ctx.runAction for actions

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use `ctx.runMutation` to call a mutation from a mutation or action.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Minimize calls from actions to queries/mutations to avoid race conditions

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use the `api` object from `convex/_generated/api.ts` to call public functions registered with `query`, `mutation`, or `action`

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Always use the new Convex function syntax (query/mutation/action with args/returns/handler)

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : When using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction` to call a function in the same file, specify a type annotation on the return value to work around TypeScript circularity limitations.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use `ctx.runQuery` to call a query from a query, mutation, or action.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-12-15T14:44:04.593Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/workflow/actions/conversation/conversation_action.ts:47-98
Timestamp: 2025-12-15T14:44:04.593Z
Learning: In Convex action files under services/platform/convex/workflow/actions/**, prefer maintaining a separate TypeScript type for the action parameters alongside the runtime validators (parametersValidator). The TypeScript type provides IDE support and compile-time checking, while the validator handles runtime validation. Document this design with a clear comment next to the type/validator pair explaining the rationale (e.g., separate types for static typing vs runtime checks, and how they relate). This pattern applies across all actions in this directory, not just a single file.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-12-15T14:44:09.823Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/workflow/actions/conversation/conversation_action.ts:47-98
Timestamp: 2025-12-15T14:44:09.823Z
Learning: In Convex action files (services/platform/convex/workflow/actions/**/), maintaining a separate TypeScript type alongside the parametersValidator is an acceptable pattern when documented. The TypeScript type provides IDE support and compile-time checking, while the validator provides runtime validation. This intentional separation should be documented with a comment explaining the design choice.

Applied to files:

  • services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use the helper TypeScript type `Id<'tableName'>` from `./_generated/dataModel` to get the type of the id for a given table

Applied to files:

  • services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-12-02T08:13:51.424Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:51.424Z
Learning: Applies to services/*/convex/*.ts : Thin wrapper API modules (like `services/platform/convex/documents.ts`) may export multiple Convex functions as wrappers that delegate to model helpers, but must not contain business logic and must only perform argument/return validation and delegation

Applied to files:

  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use the `internal` object from `convex/_generated/api.ts` to call internal functions registered with `internalQuery`, `internalMutation`, or `internalAction`

Applied to files:

  • services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-12-15T14:07:40.909Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/predefined_workflows/website_scan.ts:198-198
Timestamp: 2025-12-15T14:07:40.909Z
Learning: In all workflow DSL files under predefined_workflows, do not extract duplicate runtime-evaluated template expressions (e.g., {{steps.stepA.output.data.field || steps.stepB.output.data.field}}) into set_variables steps. Such duplications are acceptable when they’re clearly maintainable and evaluated at runtime. If uncertain about maintainability, review for readability and potential refactors.

Applied to files:

  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
📚 Learning: 2025-12-15T14:07:43.458Z
Learnt from: larryro
Repo: tale-project/tale PR: 18
File: services/platform/convex/predefined_workflows/product_relationship_analysis.ts:389-393
Timestamp: 2025-12-15T14:07:43.458Z
Learning: In workflow DSL condition steps that use JEXL expressions, prefer explicit null/undefined checks via nested property access guards to prevent runtime errors. Use patterns like: (obj && obj.prop1 && obj.prop1.prop2) ? obj.prop1.prop2.value : fallback. This avoids undefined access without adding extra set_variables steps, at the cost of verbosity. Apply this guideline to files under services/platform/convex/predefined_workflows/**/*.ts where these DSL expressions are used.

Applied to files:

  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
📚 Learning: 2025-12-16T07:35:49.441Z
Learnt from: larryro
Repo: tale-project/tale PR: 19
File: services/platform/convex/predefined_workflows/circuly_sync_subscriptions.ts:82-82
Timestamp: 2025-12-16T07:35:49.441Z
Learning: In predefined workflows where the input data structure is guaranteed by the workflow (e.g., data.items is always an array), you may omit defensive null/undefined checks in JEXL expressions for readability. This guideline does not apply to general JEXL usage outside these controlled workflows. Apply this pattern only to files under services/platform/convex/predefined_workflows where the data contract is known and enforced by the workflow.

Applied to files:

  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
📚 Learning: 2025-12-16T07:35:49.397Z
Learnt from: larryro
Repo: tale-project/tale PR: 19
File: services/platform/convex/predefined_workflows/circuly_sync_products.ts:121-121
Timestamp: 2025-12-16T07:35:49.397Z
Learning: In predefined workflows (workflowType: 'predefined') where action steps guarantee a consistent data shape (e.g., query actions always return data.items as an array, even when empty), omit verbose null-safety guards in JEXL condition expressions. Use the simplified form like steps.query_existing_product.output.data.items|length > 0 instead of nested ternaries. This improves readability while relying on the guaranteed data structure from the action implementation. Apply this guidance to all TS files under services/platform/convex/predefined_workflows (and similar predefined workflow files) where the data shape is known and enforced by the action logic.

Applied to files:

  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
📚 Learning: 2025-12-16T07:35:49.136Z
Learnt from: larryro
Repo: tale-project/tale PR: 19
File: services/platform/convex/predefined_workflows/circuly_sync_subscriptions.ts:205-205
Timestamp: 2025-12-16T07:35:49.136Z
Learning: In predefined_workflows under services/platform/convex/predefined_workflows/*.ts, you can omit defensive null checks for property access in JEXL expressions when the data shape is guaranteed by the workflow. For example, after a successful fetch, steps.X.output.data.items|length can be used directly instead of (data && data.items) ? data.items|length : 0. Ensure this only applies where the execution flow guarantees the data exists; otherwise keep guards.

Applied to files:

  • services/platform/convex/predefined_workflows/product_recommendation_email.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : When using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction` to call a function in the same file, specify a type annotation on the return value to work around TypeScript circularity limitations

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use `.unique()` to get a single document from a query; this method throws an error if multiple documents match

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Use .unique() to fetch a single document and fail on multiple matches

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use `.unique()` to get a single document from a query. This method throws an error if multiple documents match.

Applied to files:

  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts
  • services/platform/convex/model/workflow_processing_records/types.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use `ctx.runQuery` to call a query from a query, mutation, or action; use `ctx.runMutation` to call a mutation from a mutation or action; use `ctx.runAction` to call an action from an action

Applied to files:

  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Register internal functions with internalQuery, internalMutation, and internalAction (imported from ./_generated/server)

Applied to files:

  • services/platform/convex/model/workflow_processing_records/run_query.ts
  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Use `v.id(tableName)` validator for document IDs, and use strict TypeScript types with `Id<'tableName'>` instead of generic string types for function arguments and returns

Applied to files:

  • services/platform/convex/workflow_processing_records.ts
  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use `internalQuery`, `internalMutation`, and `internalAction` to register internal (private) functions that can only be called by other Convex functions and are not exposed to the public API.

Applied to files:

  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use `query`, `mutation`, and `action` to register public functions that are part of the public API. Do NOT use these for sensitive internal functions.

Applied to files:

  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Always include argument and return validators for all Convex functions (query, internalQuery, mutation, internalMutation, action, internalAction)

Applied to files:

  • services/platform/convex/workflow_processing_records.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Thoughtfully organize files with public query, mutation, or action functions within the `convex/` directory following file-based routing conventions.

Applied to files:

  • services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts
📚 Learning: 2025-12-02T08:13:24.290Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.290Z
Learning: Applies to convex/**/*.{ts,tsx} : Use the helper TypeScript type `Id<'tableName'>` imported from './_generated/dataModel' to get the type of the id for a given table.

Applied to files:

  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-08-21T15:03:10.828Z
Learnt from: CR
Repo: talecorp/lanserhof PR: 0
File: .cursor/rules/supabase.mdc:0-0
Timestamp: 2025-08-21T15:03:10.828Z
Learning: Applies to supabase/types.ts : Do not edit `types.ts`; it is generated by the script

Applied to files:

  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Be strict with types, particularly around IDs of documents; use `Id<'tableName'>` rather than `string` for function arguments and returns

Applied to files:

  • services/platform/convex/model/workflow_processing_records/is_document_processed.ts
🧬 Code graph analysis (6)
services/platform/convex/model/workflow_processing_records/constants.ts (1)
services/platform/convex/model/workflow_processing_records/index.ts (1)
  • BACKOFF_NEVER_REPROCESS (9-9)
services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts (2)
services/platform/convex/model/workflow_processing_records/index.ts (2)
  • calculateCutoffTimestamp (12-12)
  • BACKOFF_NEVER_REPROCESS (9-9)
services/platform/convex/model/workflow_processing_records/constants.ts (1)
  • BACKOFF_NEVER_REPROCESS (20-20)
services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts (5)
services/platform/convex/model/workflow_processing_records/index.ts (6)
  • FindUnprocessedOpenConversationArgs (36-36)
  • FindUnprocessedOpenConversationResult (37-37)
  • findUnprocessedOpenConversation (34-34)
  • calculateCutoffTimestamp (12-12)
  • findAndClaimUnprocessed (24-24)
  • getLatestConversationMessage (53-53)
services/platform/convex/workflow_processing_records.ts (1)
  • findUnprocessedOpenConversation (37-46)
services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts (1)
  • calculateCutoffTimestamp (12-21)
services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts (1)
  • findAndClaimUnprocessed (42-94)
services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts (1)
  • getLatestConversationMessage (31-44)
services/platform/convex/model/workflow_processing_records/run_query.ts (1)
services/platform/convex/model/workflow_processing_records/index.ts (1)
  • FindAndClaimUnprocessedArgs (26-26)
services/platform/convex/model/workflow_processing_records/find_unprocessed.ts (4)
services/platform/convex/model/workflow_processing_records/types.ts (1)
  • TableName (8-16)
services/platform/convex/workflow_processing_records.ts (1)
  • findUnprocessed (24-34)
services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts (1)
  • calculateCutoffTimestamp (12-21)
services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts (1)
  • findAndClaimUnprocessed (42-94)
services/platform/convex/model/workflow_processing_records/types.ts (1)
services/platform/convex/model/workflow_processing_records/index.ts (2)
  • FindAndClaimUnprocessedArgs (26-26)
  • FindAndClaimUnprocessedResult (27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build Docker Image
🔇 Additional comments (40)
services/platform/convex/predefined_workflows/product_recommendation_email.ts (1)

189-189: LGTM!

The maxSteps increase to 20 is appropriate for this workflow. With two tools (customer_search and rag_search) available, the LLM may need multiple tool-calling iterations before generating the final email content. This provides sufficient room for tool orchestration while remaining bounded.

services/platform/convex/model/workflow_processing_records/get_latest_conversation_message.ts (1)

28-29: LGTM!

The import path updates correctly reflect the directory flattening from model/workflow_processing_records/helpers/ to model/workflow_processing_records/. The relative paths are now shorter and accurate.

services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts (1)

27-27: LGTM!

The optional status field with the 'in_progress' | 'completed' union type correctly implements the new processing state tracking. The optional nature ensures backward compatibility with existing records.

services/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.ts (1)

31-33: LGTM!

The import path updates correctly reflect the directory restructuring. The TableName import now comes from the same directory (./types), and the generated imports have the appropriate shorter relative path.

services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts (1)

20-32: Atomic claiming is correctly implemented in the mutation.

The mutation properly implements atomic claim semantics through the recordClaimed() helper, which:

  • Queries for existing workflowProcessingRecords entries using the (tableName, recordId, wfDefinitionId) index
  • Atomically marks records as in_progress by either patching existing entries or creating new ones
  • Runs entirely within a single Convex mutation transaction with serializable isolation

The action correctly uses ctx.runMutation to invoke the mutation, preventing race conditions through Convex's transaction model. Error handling gracefully returns null if claiming fails, allowing retry logic to function properly.

services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts (1)

14-25: The atomic claim semantics are properly implemented.

Mutations run transactionally. The mutation implementation correctly handles atomic claiming through recordClaimed, which atomically updates a processing record's status to 'in_progress'. The implementation checks for an existing processing entry first (preventing duplicate claims) and either patches or creates a new entry within the same mutation transaction.

The implementation of optimistic concurrency control in Convex instead provides true serializability and will yield correct results regardless of what transactions are issued concurrently. The conversion from ctx.runQuery to ctx.runMutation correctly implements the required atomic semantics for preventing concurrent processing of the same record.

services/platform/convex/model/workflow_processing_records/is_document_processed.ts (1)

61-68: LGTM!

The early return for null records and the clear comment explaining the status field semantics improve code clarity. The logic correctly treats both in_progress and completed records as "processed" (to be skipped), which aligns with the atomic claim mechanism.

services/platform/convex/schema.ts (1)

786-820: Schema change for atomic locking looks good.

The optional status field with 'in_progress' | 'completed' union correctly supports the atomic claim/lock mechanism while maintaining backwards compatibility with existing records. The existing indexes (by_record, by_org_table_wfDefinition_processedAt) should be sufficient for the current query patterns since claims are filtered by processedAt rather than status.

services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts (1)

15-29: LGTM! Atomic claim mechanism correctly implemented.

The switch from ctx.runQuery to ctx.runMutation is the key change that enables atomic claiming of records, preventing race conditions where multiple workflow instances could process the same record. The updated comments clearly document this behavior.

Based on learnings, this follows the Convex best practice of minimizing calls from actions to queries/mutations to avoid race conditions.

services/platform/convex/model/workflow_processing_records/record_processed.ts (1)

43-64: LGTM! Correct state transition logic.

The function correctly handles both scenarios:

  1. Transitioning an existing in_progress record to completed
  2. Creating a new completed record directly (backwards-compatible path for records processed without prior claiming)

Using a single now timestamp ensures consistency across the operation.

services/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.ts (1)

1-21: LGTM! Clean utility implementation.

The cutoff timestamp calculation correctly handles the special BACKOFF_NEVER_REPROCESS case by returning epoch, ensuring any record with a processedAt value is considered processed. The normal path correctly subtracts hours from the current time.

services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts (1)

21-55: LGTM! Clean refactor to atomic claiming pattern.

The function is correctly refactored to:

  1. Use MutationCtx for atomic claiming operations
  2. Leverage the shared findAndClaimUnprocessed utility with a custom buildQuery
  3. Use calculateCutoffTimestamp for consistent cutoff calculation
  4. Return a single document result instead of an array with count

The buildQuery callback correctly handles the resume pagination pattern with _creationTime comparison.

services/platform/convex/model/workflow_processing_records/types.ts (1)

18-61: LGTM! Clean type refactoring for single-document claim semantics.

The renamed interfaces (FindAndClaimUnprocessedArgs, FindAndClaimUnprocessedResult) accurately reflect the new atomic claim behavior. The change from array-based results to document: T | null is appropriate for claim-one-at-a-time semantics, which helps prevent race conditions.

services/platform/convex/model/workflow_processing_records/record_claimed.ts (2)

21-69: Well-structured atomic claim implementation.

The upsert pattern (query-then-patch-or-insert) correctly implements the claim mechanism. Using .first() is appropriate here since the composite key should be unique. The function properly returns the record ID for both new and existing records.


36-45: Index verification complete—no action needed.

The by_record index is correctly defined in the schema as ['tableName', 'recordId', 'wfDefinitionId'], matching the query's equality predicates in the exact order required by Convex. The implementation is sound.

services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts (2)

21-55: Clean refactoring to use the centralized claim utility.

The function correctly delegates to findAndClaimUnprocessed while maintaining the conversation-specific logic (open status filter and inbound message check). The context type change from QueryCtx to MutationCtx is appropriate since claiming requires mutation capability.


34-48: No issues found with the _creationTime filter usage.

The _creationTime field is automatically added to the end of every index, so using .gt('_creationTime', resumeFrom) within the index query is valid and efficient. The documented pattern explicitly shows this pattern in action with .withIndex() queries.

services/platform/convex/model/workflow_processing_records/run_query.ts (2)

15-20: Good separation of concerns keeping runQuery read-only.

The function correctly uses QueryCtx since it only performs reads (iterating candidates, checking processing status, applying filters). The actual claiming is handled separately by the caller (findAndClaimUnprocessed), maintaining clean separation between query and mutation logic.


1-2: Import paths updated correctly for flattened directory structure.

The imports reflect the directory refactor from helpers/ subdirectory to the flat workflow_processing_records/ structure.

services/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.ts (3)

1-31: Excellent documentation with practical example.

The JSDoc provides clear documentation including a complete usage example demonstrating the query builder pattern and additional filter. This will help maintainers understand how to use this utility correctly.


55-63: Smart two-phase approach for document discovery.

The resume-point optimization followed by a full-scan fallback is well-designed. This allows the backoff mechanism to re-surface older documents whose processing records have aged past the cutoffTimestamp, while still optimizing the common case where newer documents should be processed first.


74-93: Appropriate best-effort claim with graceful error handling.

The try-catch pattern correctly implements "best-effort" claiming semantics. On failure, returning { document: null } allows the scheduler to retry later, which is the right behavior for transient conflicts. The error logging includes sufficient context for debugging.

services/platform/convex/model/workflow_processing_records/index.ts (7)

5-9: LGTM! Clean constant export.

The new constants export is well-organized and supports the backoff handling mechanism introduced in this PR.


11-12: LGTM! Appropriate helper extraction.

The extracted calculateCutoffTimestamp helper promotes code reuse across the module.


21-22: LGTM! Supports atomic claiming.

The new recordClaimed export is a key part of the atomic claim/lock mechanism introduced in this PR.


24-28: LGTM! Renamed function with clearer intent.

The rename from findUnprocessedWithCustomQuery to findAndClaimUnprocessed better expresses the atomic claiming behavior.


47-48: LGTM! Path updated for flattened structure.

The export path correctly reflects the flattened directory structure mentioned in the PR objectives.


50-51: LGTM! Path updated for flattened structure.

The export path correctly reflects the flattened directory structure.


53-53: LGTM! Path updated for flattened structure.

The export path correctly reflects the flattened directory structure.

services/platform/convex/model/workflow_processing_records/find_unprocessed.ts (5)

1-6: LGTM! Clear documentation.

The updated comments clearly explain the function's purpose and guide developers to use findAndClaimUnprocessed directly for custom queries.


8-11: LGTM! Context type updated for mutation semantics.

Changing from QueryCtx to MutationCtx is correct and necessary for the atomic claim/lock mechanism introduced in this PR.


13-22: LGTM! Type definitions updated for single-document semantics.

The removal of the limit parameter and the change from array-based to single-document return type correctly reflect the new atomic claim pattern described in the PR objectives.


24-30: LGTM! Clean use of extracted helper.

The delegation to calculateCutoffTimestamp promotes code reuse and maintains a clean separation of concerns.


32-50: LGTM! Correct delegation to findAndClaimUnprocessed.

The delegation correctly passes all required parameters and the buildQuery callback properly handles both the resume-from-checkpoint and full-scan scenarios with appropriate index usage.

services/platform/convex/workflow_processing_records.ts (6)

1-21: LGTM! Clear documentation and centralized validation.

The updated file header clearly documents the mutation-based atomic claiming approach, and the centralized tableNameValidator improves maintainability and type safety.


23-34: LGTM! Correctly converted to mutation with updated return type.

The conversion from internalQuery to internalMutation and the updated return type from array-based to single-document format correctly implement the atomic claim/lock mechanism.


36-46: LGTM! Consistent mutation pattern.

The function follows the same pattern as findUnprocessed, correctly implementing atomic claiming with appropriate return type.


48-63: LGTM! Consistent mutation pattern.

The function follows the same pattern, correctly implementing atomic claiming with single-object return semantics.


65-78: LGTM! Proper use of validator.

The use of tableNameValidator maintains consistency with other functions and ensures type safety across the API.


80-88: LGTM! Correctly remains a query.

Since getProcessingRecordById is a read-only operation that doesn't require claiming/locking, it correctly remains an internalQuery rather than being converted to a mutation.

Comment thread services/platform/convex/model/workflow_processing_records/record_claimed.ts Outdated
Comment thread services/platform/convex/schema.ts Outdated
Comment thread services/platform/convex/workflow/helpers/validation/variables/action_schemas.ts Outdated
- Fix inconsistent indentation (tabs vs spaces) in:
  - record_processed.ts
  - record_claimed.ts
  - schema.ts
  - action_schemas.ts

- Update JSDoc example in constants.ts to use renamed function findAndClaimUnprocessed

- Add console.warn logging in find_and_claim_unprocessed.ts when found document is missing _id

- Add input validation in calculate_cutoff_timestamp.ts to handle NaN/Infinity edge cases

- Add description to status field in action_schemas.ts documenting allowed values
@larryro
Copy link
Copy Markdown
Collaborator Author

larryro commented Dec 18, 2025

I've addressed all the CodeRabbit review comments in commit 754871d. Here's a summary of the fixes:

1. Indentation fixes (tabs → spaces)

  • record_processed.ts (lines 32-64): Fixed inconsistent tab-based indentation
  • record_claimed.ts (lines 47-56): Fixed tabs in the if (existing) block
  • schema.ts (lines 796-801): Fixed tabs in the workflowProcessingRecords table definition
  • action_schemas.ts (lines 245-246): Fixed tabs in processedAt and status fields

2. JSDoc example update

  • constants.ts: Updated the example to use the renamed function findAndClaimUnprocessed instead of the old findUnprocessed

3. Warning log for missing _id

  • find_and_claim_unprocessed.ts (line 70-75): Added console.warn when a found document is missing _id to make debugging easier

4. Input validation for backoffHours

  • calculate_cutoff_timestamp.ts: Added defensive validation to throw an error if backoffHours is NaN or Infinity (but not BACKOFF_NEVER_REPROCESS)

5. Status field documentation

  • action_schemas.ts: Added description: 'in_progress | completed' to the status field to document allowed values

Note: The comment about updating the inline comment for maxSteps default to 20 (in validate_and_normalize_config.ts) was marked as informational/trivial and the existing code is already clear about this being a default value, so no changes were made there.

@larryro larryro merged commit a130dc1 into main Dec 18, 2025
2 checks passed
@larryro larryro deleted the enhance-workflow-processing_records branch December 18, 2025 06:22
yannickmonney pushed a commit that referenced this pull request Apr 8, 2026
larryro added a commit that referenced this pull request May 9, 2026
- web/use-document-meta + legal-page: thread frontmatter.noindex through
  to the meta hook so legal pages actually emit
  `<meta name=robots content=noindex,nofollow>`. Pre-fix, every legal doc
  declared noindex: true in YAML but the wrapper hook ignored it and
  every legal URL shipped indexable. Round-2 review CRITICAL #26 / F.1.

- chat/legal-hold-indicator: replace banned toLocaleDateString() call
  with useFormatDate(). AGENTS.md prohibits toLocale*; pre-fix, the
  placedAt date ignored the configured locale + timezone. Round-2 review
  CRITICAL #23 / F.2.

- legal-hold/place-hold-dialog: defense-in-depth `if (!orgConfirmed)
  return;` at the top of onSubmit so a determined user (devtools
  removeAttribute, programmatic submit) cannot bypass the org-wide hold
  typed-confirmation gate. Round-2 review CRITICAL #19 / F.3.

- chat/chat-history-sidebar: archived list now uses isThreadHeld() like
  the active list — pre-fix, the archived section's heldThreadIds.has()
  ignored orgWideHeld so org-wide holds rendered no lock indicator on
  archived threads.

- chat/chat-actions: unarchive button tooltip now swaps to
  legalHold.badges.blockedByHold when held, mirroring archive/delete.
  Pre-fix, a held thread's disabled unarchive button showed only the
  generic "Unarchive" tooltip with no explanation.

- governance/hooks/mutations: useRestoreSoftDeletedRow's invalidation
  predicate now reads queryKey[1] (the convexQuery function name) instead
  of queryKey[0] (always the literal 'convexQuery'). Pre-fix, the
  predicate never matched and post-restore cache was stale on multi-page
  trash views.

Lint and typecheck clean.
larryro added a commit that referenced this pull request May 9, 2026
- web/use-document-meta + legal-page: thread frontmatter.noindex through
  to the meta hook so legal pages actually emit
  `<meta name=robots content=noindex,nofollow>`. Pre-fix, every legal doc
  declared noindex: true in YAML but the wrapper hook ignored it and
  every legal URL shipped indexable. Round-2 review CRITICAL #26 / F.1.

- chat/legal-hold-indicator: replace banned toLocaleDateString() call
  with useFormatDate(). AGENTS.md prohibits toLocale*; pre-fix, the
  placedAt date ignored the configured locale + timezone. Round-2 review
  CRITICAL #23 / F.2.

- legal-hold/place-hold-dialog: defense-in-depth `if (!orgConfirmed)
  return;` at the top of onSubmit so a determined user (devtools
  removeAttribute, programmatic submit) cannot bypass the org-wide hold
  typed-confirmation gate. Round-2 review CRITICAL #19 / F.3.

- chat/chat-history-sidebar: archived list now uses isThreadHeld() like
  the active list — pre-fix, the archived section's heldThreadIds.has()
  ignored orgWideHeld so org-wide holds rendered no lock indicator on
  archived threads.

- chat/chat-actions: unarchive button tooltip now swaps to
  legalHold.badges.blockedByHold when held, mirroring archive/delete.
  Pre-fix, a held thread's disabled unarchive button showed only the
  generic "Unarchive" tooltip with no explanation.

- governance/hooks/mutations: useRestoreSoftDeletedRow's invalidation
  predicate now reads queryKey[1] (the convexQuery function name) instead
  of queryKey[0] (always the literal 'convexQuery'). Pre-fix, the
  predicate never matched and post-restore cache was stale on multi-page
  trash views.

Lint and typecheck clean.
larryro added a commit that referenced this pull request May 17, 2026
Closes #3, #19, #20, #21, #22, #23, #24, #25, #26, #29, #39 — frontend
audio UX + resolver tests.

- `message-bubble.tsx` renders a single stable `<VoiceOutputIndicator>`
  per assistant message instead of three separate mounts (inline-
  streaming + two toolbar copies). The previous shape unmounted the
  inline indicator at streaming-end → triggered `stop()` → mounted a
  fresh toolbar indicator with a `mountTimeRef` captured AFTER all
  chunks were created → auto-play short-circuited and the user heard
  silence at the stream-end boundary. The single mount keeps
  `mountTimeRef` stable across both phases. (#3)
- `use-voice-output.ts` tracks every retry `setTimeout` id in a `Set`
  ref and clears them on unmount + on message change. The prior code
  let the 1.5s backoff timer fire after unmount and re-invoke
  `synthesize` against a dead component. (#19)
- `use-voice-output.ts` caps the synthesis queue at
  `MAX_TTS_QUEUE_DEPTH = 50`. When full, drops the new task and
  surfaces `QUEUE_OVERFLOW` via the error sink so the user sees why
  playback paused. `MAX_IN_FLIGHT` previously throttled concurrent
  dispatch but did not bound queue depth. (#20)
- `use-voice-output.ts` catch branch now falls back to
  `'UNKNOWN_NETWORK'` when `extractConvexErrorCode` returns undefined
  (network drop, action timeout). Previously the only signal was
  `console.error`; the indicator stayed stuck with no actionable
  message. (#21)
- `use-voice-output-player.ts` re-calls `primeAudio(el)` at the start
  of every `play()` invocation and drops the `el.load()` in `stop()`.
  Together these stop iOS Safari from expiring the user-activation
  token between messages of a session. (#22)
- `voice-output-context.tsx` + `prime-audio.ts`: per-provider audio
  element ownership. Each `<VoiceOutputProvider>` constructs its own
  `<audio>` via `useMemo` and exposes it via `useVoiceAudioElement()`.
  The prior module-level singleton meant arena split-view's two
  providers stomped each other's `src` mid-playback. `primeAudio(el?)`
  now takes the element to pre-warm; callers without a provider scope
  (settings page) call it with `undefined` and only the AudioContext
  is banked. (#23)
- `voice-output-indicator.tsx` classifies error codes into
  `retryable | config | terminal`. Config codes (NO_PROVIDER,
  HOST_POLICY, forbidden) render a `<Link>` to Settings → AI
  providers; terminal codes (BUDGET_EXCEEDED, QUEUE_OVERFLOW, char-
  cap) render a non-interactive `<Badge>`. Only retryable codes keep
  the click-to-retry button. Stops the tap→fail→tap→fail loop on
  unrecoverable errors. (#24)
- `voice-output-announcer.tsx` now reads `{ state, errorCode }` from
  the announcer store and speaks the per-code reason on transitions
  into `'error'` (e.g. "Voice provider not configured"). Screen-
  reader users on touch devices — where the indicator's per-code
  tooltip is unreachable — now hear the actionable reason instead of
  the generic "Voice output failed". (#25)
- `personalization-settings.tsx` composes the `providerUnavailable`
  hint into the Switch's `description` prop (a ReactNode) when
  `providerAvailable === false`. The hint now lands in the same
  `aria-describedby` block as the base description, so SR focus on
  the Switch reads it. The duplicate sibling `<Text>` is removed. (#26)
- `voice-output-announcer.tsx` drains announcements through a small
  queue with a 1500ms hold per entry. Rapid transitions
  (playing → blocked → error in <1500ms) no longer clobber the
  previous text mid-utterance; each entry plays in order. (#39)
- `resolve_tts_model.test.ts` adds the missing call-contract assertions
  (tag=text-to-speech, orgSlug propagation, providerName propagation
  on a pinned-provider call) and three failure-path tests that pin
  the resolver's re-throw behaviour for UNKNOWN_MODEL,
  UNKNOWN_PROVIDER, and plain rejections. Without these, a regression
  that hard-coded `tag: 'chat'` or dropped `orgSlug` would have passed
  every prior test silently. (#29)
- i18n: `voiceOutputErrorConfig`, `voiceOutputErrorOpenSettings`,
  `voiceOutputErrorQueueOverflow`, `voiceOutputErrorNetwork` added to
  en/de/fr. The pre-existing orphan `voiceOutputErrorProvider` is
  removed (superseded by `voiceOutputErrorConfig`).
larryro added a commit that referenced this pull request May 17, 2026
Closes #3, #19, #20, #21, #22, #23, #24, #25, #26, #29, #39 — frontend
audio UX + resolver tests.

- `message-bubble.tsx` renders a single stable `<VoiceOutputIndicator>`
  per assistant message instead of three separate mounts (inline-
  streaming + two toolbar copies). The previous shape unmounted the
  inline indicator at streaming-end → triggered `stop()` → mounted a
  fresh toolbar indicator with a `mountTimeRef` captured AFTER all
  chunks were created → auto-play short-circuited and the user heard
  silence at the stream-end boundary. The single mount keeps
  `mountTimeRef` stable across both phases. (#3)
- `use-voice-output.ts` tracks every retry `setTimeout` id in a `Set`
  ref and clears them on unmount + on message change. The prior code
  let the 1.5s backoff timer fire after unmount and re-invoke
  `synthesize` against a dead component. (#19)
- `use-voice-output.ts` caps the synthesis queue at
  `MAX_TTS_QUEUE_DEPTH = 50`. When full, drops the new task and
  surfaces `QUEUE_OVERFLOW` via the error sink so the user sees why
  playback paused. `MAX_IN_FLIGHT` previously throttled concurrent
  dispatch but did not bound queue depth. (#20)
- `use-voice-output.ts` catch branch now falls back to
  `'UNKNOWN_NETWORK'` when `extractConvexErrorCode` returns undefined
  (network drop, action timeout). Previously the only signal was
  `console.error`; the indicator stayed stuck with no actionable
  message. (#21)
- `use-voice-output-player.ts` re-calls `primeAudio(el)` at the start
  of every `play()` invocation and drops the `el.load()` in `stop()`.
  Together these stop iOS Safari from expiring the user-activation
  token between messages of a session. (#22)
- `voice-output-context.tsx` + `prime-audio.ts`: per-provider audio
  element ownership. Each `<VoiceOutputProvider>` constructs its own
  `<audio>` via `useMemo` and exposes it via `useVoiceAudioElement()`.
  The prior module-level singleton meant arena split-view's two
  providers stomped each other's `src` mid-playback. `primeAudio(el?)`
  now takes the element to pre-warm; callers without a provider scope
  (settings page) call it with `undefined` and only the AudioContext
  is banked. (#23)
- `voice-output-indicator.tsx` classifies error codes into
  `retryable | config | terminal`. Config codes (NO_PROVIDER,
  HOST_POLICY, forbidden) render a `<Link>` to Settings → AI
  providers; terminal codes (BUDGET_EXCEEDED, QUEUE_OVERFLOW, char-
  cap) render a non-interactive `<Badge>`. Only retryable codes keep
  the click-to-retry button. Stops the tap→fail→tap→fail loop on
  unrecoverable errors. (#24)
- `voice-output-announcer.tsx` now reads `{ state, errorCode }` from
  the announcer store and speaks the per-code reason on transitions
  into `'error'` (e.g. "Voice provider not configured"). Screen-
  reader users on touch devices — where the indicator's per-code
  tooltip is unreachable — now hear the actionable reason instead of
  the generic "Voice output failed". (#25)
- `personalization-settings.tsx` composes the `providerUnavailable`
  hint into the Switch's `description` prop (a ReactNode) when
  `providerAvailable === false`. The hint now lands in the same
  `aria-describedby` block as the base description, so SR focus on
  the Switch reads it. The duplicate sibling `<Text>` is removed. (#26)
- `voice-output-announcer.tsx` drains announcements through a small
  queue with a 1500ms hold per entry. Rapid transitions
  (playing → blocked → error in <1500ms) no longer clobber the
  previous text mid-utterance; each entry plays in order. (#39)
- `resolve_tts_model.test.ts` adds the missing call-contract assertions
  (tag=text-to-speech, orgSlug propagation, providerName propagation
  on a pinned-provider call) and three failure-path tests that pin
  the resolver's re-throw behaviour for UNKNOWN_MODEL,
  UNKNOWN_PROVIDER, and plain rejections. Without these, a regression
  that hard-coded `tag: 'chat'` or dropped `orgSlug` would have passed
  every prior test silently. (#29)
- i18n: `voiceOutputErrorConfig`, `voiceOutputErrorOpenSettings`,
  `voiceOutputErrorQueueOverflow`, `voiceOutputErrorNetwork` added to
  en/de/fr. The pre-existing orphan `voiceOutputErrorProvider` is
  removed (superseded by `voiceOutputErrorConfig`).
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