@hotmeshio/long-tail 0.4.24 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- ─── lt_escalations index audit ────────────────────────────────────────────
|
|
2
|
+
--
|
|
3
|
+
-- Motivation (hotmesh 0.20.0, PR perf/claim-path-hotspots):
|
|
4
|
+
-- Each stream/queue row pays index maintenance on every INSERT and on every
|
|
5
|
+
-- non-HOT UPDATE that touches an indexed column. The HITL claim path is
|
|
6
|
+
-- UPDATE-heavy (claim/resolve/release touch status, assigned_to,
|
|
7
|
+
-- assigned_until, claimed_at, priority, metadata, role — nearly all indexed),
|
|
8
|
+
-- so every claim is a non-HOT update that maintains all 18 btree indexes plus
|
|
9
|
+
-- the metadata GIN. 0.20.0 cut redundant stream-table indexes for exactly this
|
|
10
|
+
-- reason; this migration applies the same discipline to lt_escalations.
|
|
11
|
+
--
|
|
12
|
+
-- Scope: only drop indexes that are PROVABLY redundant — a strict leading-prefix
|
|
13
|
+
-- subset of another index whose partial predicate is no stricter. Anything that
|
|
14
|
+
-- merely "looks" duplicative is left in place and flagged below for EXPLAIN-driven
|
|
15
|
+
-- review against production data, not dropped speculatively.
|
|
16
|
+
|
|
17
|
+
-- 1. idx_lt_escalations_role_type (role, status, type, created_at DESC)
|
|
18
|
+
-- is an exact leading prefix of
|
|
19
|
+
-- idx_lt_escalations_role_subtype (role, status, type, subtype, created_at DESC).
|
|
20
|
+
-- Every query the former can serve (equality/range on role,status,type and the
|
|
21
|
+
-- created_at ordering) is served by the latter scanning the same prefix.
|
|
22
|
+
DROP INDEX IF EXISTS idx_lt_escalations_role_type;
|
|
23
|
+
|
|
24
|
+
-- 2. idx_lt_escalations_origin_id (origin_id) WHERE origin_id IS NOT NULL
|
|
25
|
+
-- is fully covered by idx_lt_escalations_origin (origin_id, created_at DESC):
|
|
26
|
+
-- `origin_id = $1` implies `origin_id IS NOT NULL`, origin_id leads the composite,
|
|
27
|
+
-- and the composite additionally serves the `ORDER BY created_at DESC` used by
|
|
28
|
+
-- GET_ESCALATIONS_BY_ORIGIN_ID. The partial index adds no reachable plan.
|
|
29
|
+
DROP INDEX IF EXISTS idx_lt_escalations_origin_id;
|
|
30
|
+
|
|
31
|
+
-- ── Review candidates (NOT dropped — verify with EXPLAIN on production volume) ──
|
|
32
|
+
--
|
|
33
|
+
-- These overlap but are not provably redundant. Confirm with EXPLAIN (ANALYZE,
|
|
34
|
+
-- BUFFERS) on representative data before removing any of them:
|
|
35
|
+
--
|
|
36
|
+
-- idx_lt_escalations_available (status, role, assigned_until, created_at DESC)
|
|
37
|
+
-- vs idx_lt_escalations_available_v2 (role, priority, created_at DESC)
|
|
38
|
+
-- WHERE status = 'pending'
|
|
39
|
+
-- The "_v2" naming implies intent to supersede v1, but v1 is full-table and
|
|
40
|
+
-- v1 alone positions assigned_until for the available-pool predicate. Confirm
|
|
41
|
+
-- which (if either) the listAvailableEscalations plan actually uses.
|
|
42
|
+
--
|
|
43
|
+
-- idx_lt_escalations_status (status, created_at DESC)
|
|
44
|
+
-- Partially overlaps status-leading composites, but is the only index that
|
|
45
|
+
-- serves `status = $1 ORDER BY created_at DESC` index-ordered for non-pending
|
|
46
|
+
-- statuses. Keep unless EXPLAIN shows it unused.
|
|
47
|
+
--
|
|
48
|
+
-- idx_lt_escalations_priority_desc (priority DESC, created_at DESC)
|
|
49
|
+
-- Backs the user-selectable `sort_by=priority` dashboard sort (SORTABLE_COLUMNS).
|
|
50
|
+
-- Keep while that sort is exposed.
|
|
@@ -26,7 +26,7 @@ export declare const FIND_BY_METADATA = "SELECT *, COUNT(*) OVER() AS _total\nFR
|
|
|
26
26
|
* $1 = metadata filter (jsonb), $2 = userId, $3 = durationMinutes,
|
|
27
27
|
* $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
|
|
28
28
|
*/
|
|
29
|
-
export declare const CLAIM_BY_METADATA_GUARDED = "WITH target AS (\n SELECT id, assigned_to\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND (assigned_to IS NULL OR assigned_until <= NOW() OR assigned_to = $2)\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE SKIP LOCKED\n),\nupdated AS (\n UPDATE lt_escalations e\n SET assigned_to = $2,\n claimed_at = NOW(),\n assigned_until = NOW() + INTERVAL '1 minute' * $3,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END,\n updated_at = NOW()\n FROM target t\n WHERE e.id = t.id\n RETURNING e.*, t.assigned_to AS prev_assigned_to\n)\nSELECT *,\n (SELECT COUNT(*) FROM lt_escalations WHERE metadata @> $1::jsonb AND status = 'pending') AS candidates_exist\nFROM updated";
|
|
29
|
+
export declare const CLAIM_BY_METADATA_GUARDED = "WITH target AS MATERIALIZED (\n SELECT id, assigned_to\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND (assigned_to IS NULL OR assigned_until <= NOW() OR assigned_to = $2)\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE SKIP LOCKED\n),\nupdated AS (\n UPDATE lt_escalations e\n SET assigned_to = $2,\n claimed_at = NOW(),\n assigned_until = NOW() + INTERVAL '1 minute' * $3,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END,\n updated_at = NOW()\n FROM target t\n WHERE e.id = t.id\n RETURNING e.*, t.assigned_to AS prev_assigned_to\n)\nSELECT *,\n (SELECT COUNT(*) FROM lt_escalations WHERE metadata @> $1::jsonb AND status = 'pending') AS candidates_exist\nFROM updated";
|
|
30
30
|
/**
|
|
31
31
|
* Atomic resolve by metadata with signal guard.
|
|
32
32
|
*
|
|
@@ -39,4 +39,4 @@ export declare const CLAIM_BY_METADATA_GUARDED = "WITH target AS (\n SELECT id,
|
|
|
39
39
|
* $1 = metadata filter (jsonb), $2 = userId, $3 = resolver_payload (jsonb),
|
|
40
40
|
* $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
|
|
41
41
|
*/
|
|
42
|
-
export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS (\n SELECT *\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE\n),\nclaimed AS (\n UPDATE lt_escalations e\n SET assigned_to = COALESCE(e.assigned_to, $2),\n claimed_at = COALESCE(e.claimed_at, NOW()),\n assigned_until = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.assigned_until\n ELSE NOW() + INTERVAL '5 minutes' END,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END\n FROM target\n WHERE e.id = target.id\n AND (target.metadata->>'signal_id') IS NULL\n RETURNING e.*\n),\nresolved AS (\n UPDATE lt_escalations e\n SET status = 'resolved',\n resolved_at = NOW(),\n resolver_payload = $3,\n updated_at = NOW()\n FROM claimed\n WHERE e.id = claimed.id\n RETURNING e.*\n)\nSELECT\n resolved.*,\n target.id AS target_id,\n target.metadata->>'signal_id' AS signal_id,\n target.workflow_id AS target_workflow_id,\n target.workflow_type AS target_workflow_type,\n target.task_queue AS target_task_queue,\n CASE WHEN resolved.id IS NOT NULL THEN 'resolved' ELSE 'signal_required' END AS outcome\nFROM target\nLEFT JOIN resolved ON resolved.id = target.id";
|
|
42
|
+
export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS MATERIALIZED (\n SELECT *\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND ($5::text[] IS NULL OR role = ANY($5))\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE\n),\nclaimed AS (\n UPDATE lt_escalations e\n SET assigned_to = COALESCE(e.assigned_to, $2),\n claimed_at = COALESCE(e.claimed_at, NOW()),\n assigned_until = CASE\n WHEN e.assigned_to IS NOT NULL AND e.assigned_until > NOW() THEN e.assigned_until\n ELSE NOW() + INTERVAL '5 minutes' END,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END\n FROM target\n WHERE e.id = target.id\n AND (target.metadata->>'signal_id') IS NULL\n RETURNING e.*\n),\nresolved AS (\n UPDATE lt_escalations e\n SET status = 'resolved',\n resolved_at = NOW(),\n resolver_payload = $3,\n updated_at = NOW()\n FROM claimed\n WHERE e.id = claimed.id\n RETURNING e.*\n)\nSELECT\n resolved.*,\n target.id AS target_id,\n target.metadata->>'signal_id' AS signal_id,\n target.workflow_id AS target_workflow_id,\n target.workflow_type AS target_workflow_type,\n target.task_queue AS target_task_queue,\n CASE WHEN resolved.id IS NOT NULL THEN 'resolved' ELSE 'signal_required' END AS outcome\nFROM target\nLEFT JOIN resolved ON resolved.id = target.id";
|
|
@@ -148,7 +148,7 @@ LIMIT $3 OFFSET $4`;
|
|
|
148
148
|
* $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
|
|
149
149
|
*/
|
|
150
150
|
exports.CLAIM_BY_METADATA_GUARDED = `\
|
|
151
|
-
WITH target AS (
|
|
151
|
+
WITH target AS MATERIALIZED (
|
|
152
152
|
SELECT id, assigned_to
|
|
153
153
|
FROM lt_escalations
|
|
154
154
|
WHERE metadata @> $1::jsonb
|
|
@@ -188,7 +188,7 @@ FROM updated`;
|
|
|
188
188
|
* $4 = metadata patch (jsonb, nullable), $5 = allowed roles (text[], null = no filter)
|
|
189
189
|
*/
|
|
190
190
|
exports.RESOLVE_BY_METADATA_ATOMIC = `\
|
|
191
|
-
WITH target AS (
|
|
191
|
+
WITH target AS MATERIALIZED (
|
|
192
192
|
SELECT *
|
|
193
193
|
FROM lt_escalations
|
|
194
194
|
WHERE metadata @> $1::jsonb
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/long-tail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
71
71
|
"@aws-sdk/client-s3": "^3.1017.0",
|
|
72
72
|
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
|
73
|
-
"@hotmeshio/hotmesh": "^0.
|
|
73
|
+
"@hotmeshio/hotmesh": "^0.20.1",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
75
75
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
|
|
76
76
|
"@opentelemetry/resources": "^2.5.1",
|