@hotmeshio/long-tail 0.4.20 → 0.4.22

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.
@@ -730,6 +730,24 @@ Finds one available (pending + unassigned/expired) escalation matching the metad
730
730
  | `durationMinutes` | `number` | Claim duration (default 30) |
731
731
  | `assignee` | `string` | Claim as a Long Tail user (resolved via `getUserByExternalId`) |
732
732
  | `metadata` | `object` | Additional metadata to merge (new keys added, existing overwritten) |
733
+ | `provisionIfAbsent` | `object` | JIT-provision the assignee if they don't exist (superadmin only) |
734
+
735
+ **`provisionIfAbsent`** — when the `assignee` doesn't exist in `lt_users` or lacks the escalation's role, provision them inline:
736
+
737
+ ```json
738
+ {
739
+ "key": "orderId",
740
+ "value": "order-123",
741
+ "assignee": "jane.doe",
742
+ "provisionIfAbsent": {
743
+ "displayName": "Jane Doe",
744
+ "email": "jane@example.com",
745
+ "roles": [{ "role": "station-operator", "type": "member" }]
746
+ }
747
+ }
748
+ ```
749
+
750
+ Only callers with global escalation access (superadmin, admin/admin) can use this flag. The user is created with the declared roles if absent. If the user exists but lacks a required role, the role is added. The happy path (user exists, has role) adds zero extra queries.
733
751
 
734
752
  **Response 200:**
735
753
 
@@ -748,7 +766,9 @@ Finds one available (pending + unassigned/expired) escalation matching the metad
748
766
  POST /api/escalations/resolve-by-metadata
749
767
  ```
750
768
 
751
- Finds the pending escalation, auto-claims if unclaimed, then resolves it. Supports all five resolution paths (signal, re-run, triage, etc.).
769
+ Single atomic query finds the pending escalation by metadata, auto-claims if unclaimed, and resolves it. RBAC is enforced in the SQL WHERE clause.
770
+
771
+ **Signal guard:** If the escalation has `metadata.signal_id` (created by `conditionLT`), the SQL does NOT resolve it directly. Instead, the endpoint signals the running workflow — `conditionLT` receives the signal and resolves the escalation durably inside the workflow. This preserves the same transactional integrity as the standard resolve-by-ID path.
752
772
 
753
773
  **Body:**
754
774
 
@@ -769,4 +789,20 @@ Finds the pending escalation, auto-claims if unclaimed, then resolves it. Suppor
769
789
  | `assignee` | `string` | Resolve as a Long Tail user (resolved via `getUserByExternalId`) |
770
790
  | `metadata` | `object` | Additional metadata to merge (new keys added, existing overwritten) |
771
791
 
772
- **Response 200:** Same as standard resolve endpoint.
792
+ **Response 200 (non-signal):** Escalation resolved atomically.
793
+
794
+ ```json
795
+ {
796
+ "escalation": { "id": "...", "status": "resolved", ... }
797
+ }
798
+ ```
799
+
800
+ **Response 200 (signal-backed):** Workflow signaled; `conditionLT` resolves the escalation durably.
801
+
802
+ ```json
803
+ {
804
+ "signaled": true,
805
+ "escalationId": "...",
806
+ "workflowId": "..."
807
+ }
808
+ ```
@@ -542,14 +542,20 @@ const result = await lt.escalations.findByMetadata({
542
542
 
543
543
  ## claimByMetadata
544
544
 
545
- Find and claim an escalation by metadata key-value pair in one atomic call.
545
+ Find and claim an escalation by metadata key-value pair in one atomic call. RBAC is enforced in the SQL WHERE clause.
546
546
 
547
547
  ```typescript
548
548
  const result = await lt.escalations.claimByMetadata({
549
549
  key: 'orderId',
550
550
  value: 'order-123',
551
551
  durationMinutes: 30,
552
+ assignee: 'jane.doe',
552
553
  metadata: { claimedBy: 'jimbo', station: 'scanning' },
554
+ provisionIfAbsent: {
555
+ displayName: 'Jane Doe',
556
+ email: 'jane@example.com',
557
+ roles: [{ role: 'station-operator', type: 'member' }],
558
+ },
553
559
  });
554
560
  ```
555
561
 
@@ -562,6 +568,9 @@ const result = await lt.escalations.claimByMetadata({
562
568
  | `durationMinutes` | `number` | No | Claim duration (default: 30) |
563
569
  | `assignee` | `string` | No | Claim as a Long Tail user (resolved via `getUserByExternalId`) |
564
570
  | `metadata` | `object` | No | Merge into escalation metadata (single atomic SQL call with the claim) |
571
+ | `provisionIfAbsent` | `object` | No | JIT-provision the assignee if they don't exist or lack the required role (superadmin only) |
572
+
573
+ `provisionIfAbsent` accepts `{ displayName?, email?, roles?: [{ role, type? }] }`. Only callers with global escalation access can use this flag. The happy path (user exists, has role) adds zero extra queries.
565
574
 
566
575
  **Returns:** `LTApiResult<{ escalation, isExtension }>` -- 404 if no match, 409 if already claimed.
567
576
 
@@ -571,15 +580,26 @@ const result = await lt.escalations.claimByMetadata({
571
580
 
572
581
  ## resolveByMetadata
573
582
 
574
- Find and resolve an escalation by metadata key-value pair. Auto-claims if unclaimed. Supports all resolution paths.
583
+ Find and resolve an escalation by metadata key-value pair. Single atomic query with signal guard.
584
+
585
+ If the escalation has `metadata.signal_id` (created by `conditionLT`), the endpoint signals the running workflow instead of resolving directly in the DB. `conditionLT` receives the signal and resolves the escalation durably inside the workflow. This preserves the same transactional integrity as the standard resolve-by-ID path.
575
586
 
576
587
  ```typescript
588
+ // Non-signal escalation → resolved atomically
589
+ const result = await lt.escalations.resolveByMetadata({
590
+ key: 'orderId',
591
+ value: 'order-123',
592
+ resolverPayload: { approved: true },
593
+ });
594
+ // result.data.escalation.status === 'resolved'
595
+
596
+ // Signal-backed escalation → workflow signaled
577
597
  const result = await lt.escalations.resolveByMetadata({
578
598
  key: 'orderId',
579
599
  value: 'order-123',
580
- resolverPayload: { approved: true, targetStatus: 'completed' },
581
- metadata: { completedBy: 'jimbo' },
600
+ resolverPayload: { approved: true },
582
601
  });
602
+ // result.data.signaled === true, result.data.workflowId === '...'
583
603
  ```
584
604
 
585
605
  **Parameters:**
@@ -592,6 +612,6 @@ const result = await lt.escalations.resolveByMetadata({
592
612
  | `assignee` | `string` | No | Resolve as a Long Tail user (resolved via `getUserByExternalId`) |
593
613
  | `metadata` | `object` | No | Merge into escalation metadata before resolving |
594
614
 
595
- **Returns:** Same as `resolve` -- 404 if no match.
615
+ **Returns:** `LTApiResult<{ escalation }>` for non-signal, `LTApiResult<{ signaled, escalationId, workflowId }>` for signal-backed. 404 if no match.
596
616
 
597
617
  **Auth:** Required
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
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.4",
73
+ "@hotmeshio/hotmesh": "^0.19.5",
74
74
  "@modelcontextprotocol/sdk": "^1.27.1",
75
75
  "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
76
76
  "@opentelemetry/resources": "^2.5.1",