@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.4.24",
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.19.5",
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",