refactor(workflow-processing): add atomic claim/lock mechanism and flatten helpers structure#23
Conversation
…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
📝 WalkthroughWalkthroughThe 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 Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Comment |
There was a problem hiding this comment.
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)
⛔ Files ignored due to path filters (1)
services/platform/convex/_generated/api.d.tsis 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.tsservices/platform/convex/model/workflow_processing_records/record_claimed.tsservices/platform/convex/model/workflow_processing_records/constants.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.tsservices/platform/convex/workflow/helpers/validation/variables/action_schemas.tsservices/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.tsservices/platform/convex/model/workflow_processing_records/get_latest_conversation_message.tsservices/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.tsservices/platform/convex/schema.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.tsservices/platform/convex/predefined_workflows/product_recommendation_email.tsservices/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/model/workflow_processing_records/index.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/types.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.tsservices/platform/convex/model/workflow_processing_records/record_processed.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/record_claimed.tsservices/platform/convex/model/workflow_processing_records/constants.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.tsservices/platform/convex/workflow/helpers/validation/variables/action_schemas.tsservices/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.tsservices/platform/convex/model/workflow_processing_records/get_latest_conversation_message.tsservices/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.tsservices/platform/convex/schema.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.tsservices/platform/convex/predefined_workflows/product_recommendation_email.tsservices/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/model/workflow_processing_records/index.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/types.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.tsservices/platform/convex/model/workflow_processing_records/record_processed.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/record_claimed.tsservices/platform/convex/model/workflow_processing_records/constants.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.tsservices/platform/convex/workflow/helpers/validation/variables/action_schemas.tsservices/platform/convex/model/workflow_processing_records/get_latest_processed_creation_time.tsservices/platform/convex/model/workflow_processing_records/get_latest_conversation_message.tsservices/platform/convex/model/workflow_processing_records/calculate_cutoff_timestamp.tsservices/platform/convex/schema.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.tsservices/platform/convex/predefined_workflows/product_recommendation_email.tsservices/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/model/workflow_processing_records/index.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/types.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.tsservices/platform/convex/model/workflow_processing_records/record_processed.tsservices/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.tsservices/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.tsservices/platform/convex/workflow/helpers/validation/variables/action_schemas.tsservices/platform/convex/schema.tsservices/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.tsservices/platform/convex/model/workflow_processing_records/index.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/platform/convex/workflow_processing_records.tsservices/platform/convex/model/workflow_processing_records/find_and_claim_unprocessed.tsservices/platform/convex/model/workflow_processing_records/record_processed.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/types.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/model/workflow_processing_records/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/platform/convex/workflow_processing_records.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/get_latest_conversation_message.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/get_latest_conversation_message.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/types.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/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.tsservices/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.tsservices/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.tsservices/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/run_query.tsservices/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.tsservices/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.tsservices/platform/convex/model/workflow_processing_records/types.tsservices/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.tsservices/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.tsservices/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.tsservices/platform/convex/workflow_processing_records.tsservices/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.tsservices/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
maxStepsincrease to 20 is appropriate for this workflow. With two tools (customer_searchandrag_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/tomodel/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
statusfield 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
TableNameimport 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
workflowProcessingRecordsentries using the (tableName, recordId, wfDefinitionId) index- Atomically marks records as
in_progressby either patching existing entries or creating new ones- Runs entirely within a single Convex mutation transaction with serializable isolation
The action correctly uses
ctx.runMutationto 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.runQuerytoctx.runMutationcorrectly 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_progressandcompletedrecords 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
statusfield 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 byprocessedAtrather thanstatus.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.runQuerytoctx.runMutationis 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:
- Transitioning an existing
in_progressrecord tocompleted- Creating a new
completedrecord directly (backwards-compatible path for records processed without prior claiming)Using a single
nowtimestamp 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_REPROCESScase by returning epoch, ensuring any record with aprocessedAtvalue 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:
- Use
MutationCtxfor atomic claiming operations- Leverage the shared
findAndClaimUnprocessedutility with a custombuildQuery- Use
calculateCutoffTimestampfor consistent cutoff calculation- Return a single document result instead of an array with count
The
buildQuerycallback correctly handles the resume pagination pattern with_creationTimecomparison.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 todocument: T | nullis 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_recordindex 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
findAndClaimUnprocessedwhile maintaining the conversation-specific logic (open status filter and inbound message check). The context type change fromQueryCtxtoMutationCtxis appropriate since claiming requires mutation capability.
34-48: No issues found with the_creationTimefilter usage.The
_creationTimefield 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 keepingrunQueryread-only.The function correctly uses
QueryCtxsince 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 flatworkflow_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
calculateCutoffTimestamphelper promotes code reuse across the module.
21-22: LGTM! Supports atomic claiming.The new
recordClaimedexport 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
findUnprocessedWithCustomQuerytofindAndClaimUnprocessedbetter 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
findAndClaimUnprocesseddirectly for custom queries.
8-11: LGTM! Context type updated for mutation semantics.Changing from
QueryCtxtoMutationCtxis 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
limitparameter 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
calculateCutoffTimestamppromotes 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
buildQuerycallback 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
tableNameValidatorimproves maintainability and type safety.
23-34: LGTM! Correctly converted to mutation with updated return type.The conversion from
internalQuerytointernalMutationand 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
tableNameValidatormaintains consistency with other functions and ensures type safety across the API.
80-88: LGTM! Correctly remains a query.Since
getProcessingRecordByIdis a read-only operation that doesn't require claiming/locking, it correctly remains aninternalQueryrather than being converted to a mutation.
- 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
|
I've addressed all the CodeRabbit review comments in commit 754871d. Here's a summary of the fixes: 1. Indentation fixes (tabs → spaces)
2. JSDoc example update
3. Warning log for missing _id
4. Input validation for backoffHours
5. Status field documentation
Note: The comment about updating the inline comment for |
…atten helpers structure (#23)
- 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.
- 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.
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`).
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`).
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
statusfield toworkflowProcessingRecordsschema (in_progress|completed)findUnprocessedWithCustomQuerytofindAndClaimUnprocessedrecordClaimedfunction for explicit claim operationsctx.runMutationinstead ofctx.runQueryThis prevents race conditions where multiple concurrent workflow executions could pick up and process the same entity simultaneously.
�� Directory Structure Refactoring
model/workflow_processing_records/helpers/tomodel/workflow_processing_records/calculateCutoffTimestamphelper to centralize cutoff logicconstantsmodule withBACKOFF_NEVER_REPROCESSconstant⚙️ LLM Configuration
maxStepsfrom 10 to 20 for LLM tool callingproduct_recommendation_emailworkflowmaxStepsto allow multiple steps for tool callsSummary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.