Relationship construction hand-off
Moves the two remaining GoodJob producers of relationship construction —
CreateConversationRelationshipsJob and
CreateConversationPartRelationshipsJob — behind the API2 BullMQ workers that
already execute the work for the provider-sync manifest keys.
Contract
Conversation#detect_and_queue_conversation_relationships(subject change or create) andConversationFieldValue’s commit hook (create or string value change) POST toPOST /internal/conversation_relationships/enqueue, which adds oneprovider-conversation-field-value-linkjob keyedprovider_conversation_field_value_link_<conversationId>_<changeKey>.change_keyis the producer’s per-commit token (<record>_<id>_<updated_at iso8601(6)>): a retried delivery of the same commit dedupes onto the already-accepted job, and a later commit cannot be swallowed by a retained completed/failed key.ConversationPart#detect_and_queue_relationship_creationposts toPOST /internal/conversation_part_relationships/enqueue, which adds one part-keyed job per reference kind (provider_conversation_part_reference_link_<partId>,..._jira_link_<partId>,..._aircall_call_<partId>). Only the all-kinds callback takes this path; akindssubset stays on the GoodJob job.- The workers re-read current state at execution time (subject + all string field values, or the part’s persisted content), so ordering and merge semantics are identical to the GoodJob passes — every write is a thread merge, unique-indexed thread item, or Aircall call part import that is idempotent under replay.
- Delivery classification matches the other hand-offs: a transport error is
ambiguous (API2 may have accepted) and retries through
EnqueueApi2ConversationRelationshipsJob/EnqueueApi2ConversationPartRelationshipsJob; an explicit non-2xx falls back to the legacy GoodJob job so the commit is never dropped.
Rollback order
- PostHog flags
api2_conversation_relationshipsandapi2_conversation_part_relationships(falserestores the GoodJob producers immediately; they fail closed when PostHog is unreachable). - A clear API2 delivery failure already falls back to the GoodJob job automatically, so a worker outage degrades rather than drops.
PROVIDER_SYNC_NATIVE_EFFECTShand-back stays independent: the sync effects applier calls the part callback withvia_api2: false, so a kind returned to Rails always executes on GoodJob regardless of the flags.
Verification
-
Assert zero new
CreateConversationRelationshipsJobandCreateConversationPartRelationshipsJobexecutions in each region’s retained GoodJob window after the flag reaches 100% (retained 12-hour rows are historical). -
BullMQ:
provider-conversation-field-value-linkand the three part link queues accept and drain; no growth in failed sets beyond the existing provider-sync baseline. -
Production observation query (per region):
SELECT job_class, COUNT(*) FROM good_job_executions WHERE job_class IN ('CreateConversationRelationshipsJob', 'CreateConversationPartRelationshipsJob', 'EnqueueApi2ConversationRelationshipsJob', 'EnqueueApi2ConversationPartRelationshipsJob') AND created_at > now() - interval '12 hours' GROUP BY job_class; -
Bridge jobs should sit near zero; a sustained count means ambiguous delivery (check API2
/internal/conversation_*_relationships/enqueuehealth andinternal_api_key).