mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 12:59:13 +01:00
Add dormant provider contracts, qualified patched adapters, tenant-scoped persistence and lifecycle ownership without activating chat routes. Preserve the experimental integration as dependent PR #13038. Co-Authored-By: Paperclip <noreply@paperclip.ing>
107 lines
5.2 KiB
SQL
107 lines
5.2 KiB
SQL
-- The old partial index does not cover interaction:* keys, so this is
|
|
-- necessarily one prefix scan of agent_wakeup_requests. Drizzle applies the
|
|
-- migration transactionally: writers cannot observe the repair without the
|
|
-- replacement index, and the index cannot admit a new duplicate before the
|
|
-- repair commits. A temporary helper index would require the same large-table
|
|
-- scan while adding another transactional DDL lock, so the selective prefix
|
|
-- scan is the lower-lock upgrade path.
|
|
--
|
|
-- Preserve the most meaningful run-backed wake as the canonical key holder.
|
|
-- Terminal and actively executing duplicates keep their status and run link;
|
|
-- they are re-keyed outside the canonical namespace with audit metadata. Only
|
|
-- duplicate work that has not acquired a run and is safe to retire is marked
|
|
-- skipped. In particular, claimed/running work is never falsely cancelled.
|
|
WITH ranked AS (
|
|
SELECT
|
|
"id",
|
|
"idempotency_key" AS "original_idempotency_key",
|
|
"status" AS "previous_status",
|
|
"run_id" AS "linked_run_id",
|
|
first_value("id") OVER (
|
|
PARTITION BY "company_id", "idempotency_key"
|
|
ORDER BY
|
|
CASE
|
|
WHEN "run_id" IS NOT NULL AND "status" IN ('succeeded', 'completed', 'coalesced') THEN 0
|
|
WHEN "run_id" IS NOT NULL AND "status" IN ('running', 'claimed') THEN 1
|
|
WHEN "run_id" IS NOT NULL THEN 2
|
|
WHEN "status" IN ('running', 'claimed') THEN 3
|
|
WHEN "status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry') THEN 4
|
|
ELSE 5
|
|
END,
|
|
"requested_at" ASC,
|
|
"created_at" ASC,
|
|
"id" ASC
|
|
) AS "retained_id",
|
|
row_number() OVER (
|
|
PARTITION BY "company_id", "idempotency_key"
|
|
ORDER BY
|
|
CASE
|
|
WHEN "run_id" IS NOT NULL AND "status" IN ('succeeded', 'completed', 'coalesced') THEN 0
|
|
WHEN "run_id" IS NOT NULL AND "status" IN ('running', 'claimed') THEN 1
|
|
WHEN "run_id" IS NOT NULL THEN 2
|
|
WHEN "status" IN ('running', 'claimed') THEN 3
|
|
WHEN "status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry') THEN 4
|
|
ELSE 5
|
|
END,
|
|
"requested_at" ASC,
|
|
"created_at" ASC,
|
|
"id" ASC
|
|
) AS "ordinal"
|
|
FROM "agent_wakeup_requests"
|
|
WHERE "idempotency_key" LIKE 'interaction:%'
|
|
AND "status" NOT IN ('skipped', 'failed', 'cancelled')
|
|
), duplicates AS (
|
|
SELECT * FROM ranked WHERE "ordinal" > 1
|
|
)
|
|
UPDATE "agent_wakeup_requests" AS wake
|
|
SET
|
|
"idempotency_key" = CASE
|
|
WHEN duplicates."linked_run_id" IS NULL
|
|
AND duplicates."previous_status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry')
|
|
THEN duplicates."original_idempotency_key"
|
|
ELSE 'historical-interaction-wake-duplicate:' || wake."id"::text
|
|
END,
|
|
"status" = CASE
|
|
WHEN duplicates."linked_run_id" IS NULL
|
|
AND duplicates."previous_status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry')
|
|
THEN 'skipped'
|
|
ELSE wake."status"
|
|
END,
|
|
"finished_at" = CASE
|
|
WHEN duplicates."linked_run_id" IS NULL
|
|
AND duplicates."previous_status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry')
|
|
THEN COALESCE(wake."finished_at", now())
|
|
ELSE wake."finished_at"
|
|
END,
|
|
"error" = CASE
|
|
WHEN duplicates."linked_run_id" IS NULL
|
|
AND duplicates."previous_status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry')
|
|
THEN concat_ws(
|
|
E'\n',
|
|
NULLIF(wake."error", ''),
|
|
'Safely retired duplicate by migration 0245; retained wake request ' || duplicates."retained_id"::text
|
|
)
|
|
ELSE wake."error"
|
|
END,
|
|
"payload" = COALESCE(wake."payload", '{}'::jsonb) || jsonb_build_object(
|
|
'migrationDedupe', jsonb_build_object(
|
|
'migration', '0245_chat_interaction_wakeup_idempotency',
|
|
'retainedWakeRequestId', duplicates."retained_id",
|
|
'originalIdempotencyKey', duplicates."original_idempotency_key",
|
|
'previousStatus', duplicates."previous_status",
|
|
'linkedRunId', duplicates."linked_run_id",
|
|
'resolution', CASE
|
|
WHEN duplicates."linked_run_id" IS NULL
|
|
AND duplicates."previous_status" IN ('queued', 'deferred_issue_execution', 'retrying', 'scheduled_retry')
|
|
THEN 'retired_unstarted_duplicate'
|
|
ELSE 'rekeyed_preserving_execution_history'
|
|
END
|
|
)
|
|
),
|
|
"updated_at" = now()
|
|
FROM duplicates
|
|
WHERE wake."id" = duplicates."id";--> statement-breakpoint
|
|
DROP INDEX IF EXISTS "agent_wakeup_requests_question_response_delivery_idempotency_uq";--> statement-breakpoint
|
|
-- paperclip:migration-safety-ignore large-create-index-not-concurrently: Drizzle migrations run transactionally, so CONCURRENTLY is unavailable. The selective duplicate repair above preserves historical evidence and the expanded predicate must commit atomically before board and external-chat resolvers share the canonical interaction wake key.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "agent_wakeup_requests_question_response_delivery_idempotency_uq" ON "agent_wakeup_requests" USING btree ("company_id","idempotency_key") WHERE ("agent_wakeup_requests"."idempotency_key" LIKE 'question-response:%' OR "agent_wakeup_requests"."idempotency_key" LIKE 'interaction:%') AND "agent_wakeup_requests"."status" NOT IN ('skipped', 'failed', 'cancelled');
|