@hotmeshio/long-tail 0.1.11 → 0.1.13
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.
- package/README.md +1 -1
- package/build/api/escalations.js +7 -2
- package/build/examples/seed.js +50 -0
- package/build/lib/db/schemas/001_schema.sql +281 -106
- package/build/lib/db/schemas/002_seed.sql +56 -39
- package/build/services/mcp/client/connection.d.ts +13 -0
- package/build/services/mcp/client/connection.js +62 -0
- package/build/services/mcp/client/tools.js +20 -7
- package/build/services/mcp/server.js +31 -0
- package/build/services/yaml-workflow/workers/register.js +24 -4
- package/build/system/mcp-servers/human-queue.js +31 -0
- package/docs/cloud.md +123 -0
- package/package.json +3 -3
- package/build/lib/db/schemas/003_workflow_discovery.sql +0 -39
- package/build/lib/db/schemas/004_query_router.sql +0 -38
- package/build/lib/db/schemas/004_workflow_sets.sql +0 -29
- package/build/lib/db/schemas/005_triage_router.sql +0 -37
- package/build/lib/db/schemas/005_unique_graph_topic.sql +0 -7
- package/build/lib/db/schemas/006_oauth.sql +0 -50
- package/build/lib/db/schemas/007_security.sql +0 -27
- package/build/lib/db/schemas/008_bot_accounts.sql +0 -30
- package/build/lib/db/schemas/009_audit_trail.sql +0 -7
- package/build/lib/db/schemas/010_credential_providers.sql +0 -4
- package/build/lib/db/schemas/011_system_workflow_configs.sql +0 -37
- package/build/lib/db/schemas/012_drop_modality.sql +0 -6
- package/build/lib/db/schemas/013_execute_as.sql +0 -9
- package/build/lib/db/schemas/014_ephemeral_credentials.sql +0 -16
- package/build/lib/db/schemas/015_knowledge.sql +0 -23
- package/build/lib/db/schemas/016_streamable_http.sql +0 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Long Tail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Long Tail turns your PostgreSQL database into a workflow engine where human and AI work share the same durable execution path. Hand off, pull back, escalate, automate — without rewriting systems or losing continuity.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @hotmeshio/long-tail
|
package/build/api/escalations.js
CHANGED
|
@@ -730,7 +730,12 @@ async function resolveEscalation(input, auth) {
|
|
|
730
730
|
const handle = await client.workflow.getHandle(signalRouting.taskQueue, signalRouting.workflowType, signalRouting.workflowId);
|
|
731
731
|
await handle.signal(signalRouting.signalId, signalPayload);
|
|
732
732
|
}
|
|
733
|
-
|
|
733
|
+
// For YAML workflows, the resolve worker inside the workflow calls
|
|
734
|
+
// claim_and_resolve to close the escalation transactionally. Only resolve
|
|
735
|
+
// here for Durable workflows that lack an in-workflow resolve step.
|
|
736
|
+
if (signalRouting.engine !== 'yaml') {
|
|
737
|
+
await escalationService.resolveEscalation(escalation.id, resolverPayload);
|
|
738
|
+
}
|
|
734
739
|
(0, publish_1.publishEscalationEvent)({
|
|
735
740
|
type: 'escalation.resolved',
|
|
736
741
|
source: 'api',
|
|
@@ -740,7 +745,7 @@ async function resolveEscalation(input, auth) {
|
|
|
740
745
|
taskId: escalation.task_id,
|
|
741
746
|
escalationId: escalation.id,
|
|
742
747
|
originId: escalation.origin_id ?? undefined,
|
|
743
|
-
status: 'resolved',
|
|
748
|
+
status: signalRouting.engine === 'yaml' ? 'signaled' : 'resolved',
|
|
744
749
|
});
|
|
745
750
|
return {
|
|
746
751
|
status: 200,
|
package/build/examples/seed.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.seedExamples = seedExamples;
|
|
|
4
4
|
const hotmesh_1 = require("@hotmeshio/hotmesh");
|
|
5
5
|
const defaults_1 = require("../modules/defaults");
|
|
6
6
|
const logger_1 = require("../lib/logger");
|
|
7
|
+
const db_1 = require("../lib/db");
|
|
7
8
|
const user_1 = require("../services/user");
|
|
8
9
|
const roles_1 = require("../services/user/roles");
|
|
9
10
|
const role_1 = require("../services/role");
|
|
@@ -211,11 +212,60 @@ async function seedEscalationChains() {
|
|
|
211
212
|
}
|
|
212
213
|
logger_1.loggerRegistry.info(`[examples] escalation chains verified (${SEED_CHAINS.length} entries)`);
|
|
213
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Seed example workflow configs into lt_config_workflows.
|
|
217
|
+
* Previously done by 002_seed.sql (which ran unconditionally).
|
|
218
|
+
* Now only runs when examples: true.
|
|
219
|
+
*/
|
|
220
|
+
async function seedExampleConfigs() {
|
|
221
|
+
const pool = (0, db_1.getPool)();
|
|
222
|
+
await pool.query(`
|
|
223
|
+
INSERT INTO lt_config_workflows
|
|
224
|
+
(workflow_type, task_queue, default_role, invocable, description, tool_tags, envelope_schema, resolver_schema)
|
|
225
|
+
VALUES
|
|
226
|
+
('reviewContent', 'long-tail-examples', 'reviewer', true,
|
|
227
|
+
'Content review — AI-powered moderation with human escalation for low-confidence results',
|
|
228
|
+
ARRAY['document-processing', 'vision', 'ocr', 'translation'],
|
|
229
|
+
'{"data": {"contentId": "article-001", "content": "Content to review...", "contentType": "article"}, "metadata": {"source": "dashboard"}}'::jsonb,
|
|
230
|
+
'{"approved": true, "analysis": {"confidence": 0.95, "flags": [], "summary": "Manually reviewed and approved."}}'::jsonb),
|
|
231
|
+
('verifyDocument', 'long-tail-examples', 'reviewer', true,
|
|
232
|
+
'Document verification — AI Vision analyzes identity documents',
|
|
233
|
+
ARRAY['document-processing', 'vision', 'ocr', 'translation'],
|
|
234
|
+
'{"data": {"documentId": "doc-001", "documentUrl": "https://example.com/doc.jpg", "documentType": "drivers_license", "memberId": "member-12345"}, "metadata": {"source": "dashboard"}}'::jsonb,
|
|
235
|
+
'{"memberId": "", "extractedInfo": {}, "validationResult": "match", "confidence": 1.0}'::jsonb),
|
|
236
|
+
('processClaim', 'long-tail-examples', 'reviewer', true,
|
|
237
|
+
'Insurance claim processing — document analysis, validation, and human review',
|
|
238
|
+
ARRAY['document-processing', 'vision', 'database', 'query'],
|
|
239
|
+
'{"data": {"claimId": "CLM-2024-001", "claimantId": "POL-5551234", "claimType": "auto_collision", "amount": 12500, "documents": ["incident_report.pdf", "photo_evidence.jpg"]}, "metadata": {"source": "dashboard"}}'::jsonb,
|
|
240
|
+
'{"approved": true, "analysis": {"confidence": 0.92, "flags": [], "summary": "Documents reviewed and verified."}, "status": "resolved"}'::jsonb),
|
|
241
|
+
('kitchenSink', 'long-tail-examples', 'reviewer', true,
|
|
242
|
+
'Kitchen sink — demonstrates sleep, signals, parallel activities, escalation, and every durable primitive',
|
|
243
|
+
'{}',
|
|
244
|
+
'{"data": {"name": "World", "mode": "full"}, "metadata": {"source": "dashboard"}}'::jsonb,
|
|
245
|
+
NULL),
|
|
246
|
+
('basicSignal', 'long-tail-examples', 'reviewer', true,
|
|
247
|
+
'Signal-based escalation — workflow stays running while waiting for human input via conditionLT',
|
|
248
|
+
'{}',
|
|
249
|
+
'{"data": {"message": "Deployment approval needed for v2.1.0", "role": "reviewer"}, "metadata": {"certified": false, "source": "dashboard"}}'::jsonb,
|
|
250
|
+
'{"properties": {"approved": {"type": "boolean", "default": false, "description": "Approve this deployment?"}, "notes": {"type": "string", "default": "", "description": "Reviewer notes — visible to the workflow author"}}}'::jsonb)
|
|
251
|
+
ON CONFLICT (workflow_type) DO NOTHING
|
|
252
|
+
`);
|
|
253
|
+
// Assign roles to example workflows
|
|
254
|
+
await pool.query(`
|
|
255
|
+
INSERT INTO lt_config_roles (workflow_type, role)
|
|
256
|
+
SELECT workflow_type, unnest(ARRAY['reviewer', 'engineer', 'admin'])
|
|
257
|
+
FROM lt_config_workflows
|
|
258
|
+
WHERE workflow_type IN ('reviewContent', 'verifyDocument', 'processClaim', 'kitchenSink')
|
|
259
|
+
ON CONFLICT (workflow_type, role) DO NOTHING
|
|
260
|
+
`);
|
|
261
|
+
logger_1.loggerRegistry.info('[examples] workflow configs seeded');
|
|
262
|
+
}
|
|
214
263
|
/**
|
|
215
264
|
* Seed example workflows so the dashboard tells a story immediately.
|
|
216
265
|
* Called automatically when `examples: true` is set in the start config.
|
|
217
266
|
*/
|
|
218
267
|
async function seedExamples(client) {
|
|
268
|
+
await seedExampleConfigs();
|
|
219
269
|
await seedRoles();
|
|
220
270
|
await seedUsers();
|
|
221
271
|
await seedEscalationChains();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
-- Long Tail Workflows: Schema
|
|
2
|
-
--
|
|
2
|
+
-- All tables, indexes, triggers, and functions.
|
|
3
3
|
|
|
4
4
|
-- ─── updated_at trigger ─────────────────────────────────────────────────────
|
|
5
5
|
|
|
@@ -28,28 +28,31 @@ ON CONFLICT DO NOTHING;
|
|
|
28
28
|
-- ─── lt_tasks ────────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
30
30
|
CREATE TABLE IF NOT EXISTS lt_tasks (
|
|
31
|
-
id
|
|
32
|
-
workflow_id
|
|
33
|
-
workflow_type
|
|
34
|
-
lt_type
|
|
35
|
-
task_queue
|
|
36
|
-
status
|
|
37
|
-
priority
|
|
38
|
-
signal_id
|
|
31
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
workflow_id TEXT NOT NULL,
|
|
33
|
+
workflow_type TEXT NOT NULL,
|
|
34
|
+
lt_type TEXT NOT NULL,
|
|
35
|
+
task_queue TEXT,
|
|
36
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
37
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
38
|
+
signal_id TEXT NOT NULL,
|
|
39
39
|
parent_workflow_id TEXT NOT NULL,
|
|
40
|
-
origin_id
|
|
41
|
-
parent_id
|
|
42
|
-
started_at
|
|
43
|
-
completed_at
|
|
44
|
-
envelope
|
|
45
|
-
metadata
|
|
46
|
-
error
|
|
47
|
-
milestones
|
|
48
|
-
data
|
|
49
|
-
trace_id
|
|
50
|
-
span_id
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
origin_id TEXT,
|
|
41
|
+
parent_id TEXT,
|
|
42
|
+
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
43
|
+
completed_at TIMESTAMPTZ,
|
|
44
|
+
envelope TEXT NOT NULL,
|
|
45
|
+
metadata JSONB,
|
|
46
|
+
error TEXT,
|
|
47
|
+
milestones JSONB NOT NULL DEFAULT '[]'::JSONB,
|
|
48
|
+
data TEXT,
|
|
49
|
+
trace_id TEXT,
|
|
50
|
+
span_id TEXT,
|
|
51
|
+
initiated_by UUID,
|
|
52
|
+
principal_type TEXT DEFAULT 'user',
|
|
53
|
+
executing_as TEXT,
|
|
54
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
55
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
53
56
|
);
|
|
54
57
|
|
|
55
58
|
CREATE INDEX IF NOT EXISTS idx_lt_tasks_status_type ON lt_tasks (status, workflow_type, created_at DESC);
|
|
@@ -61,39 +64,80 @@ CREATE INDEX IF NOT EXISTS idx_lt_tasks_origin ON lt_tasks (origin_id, created_a
|
|
|
61
64
|
CREATE INDEX IF NOT EXISTS idx_lt_tasks_workflow_id ON lt_tasks (workflow_id);
|
|
62
65
|
CREATE INDEX IF NOT EXISTS idx_lt_tasks_origin_id ON lt_tasks (origin_id) WHERE origin_id IS NOT NULL;
|
|
63
66
|
CREATE INDEX IF NOT EXISTS idx_lt_tasks_trace ON lt_tasks (trace_id) WHERE trace_id IS NOT NULL;
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_lt_tasks_initiated_by ON lt_tasks (initiated_by) WHERE initiated_by IS NOT NULL;
|
|
64
68
|
|
|
65
69
|
CREATE OR REPLACE TRIGGER trg_lt_tasks_updated_at
|
|
66
70
|
BEFORE UPDATE ON lt_tasks
|
|
67
71
|
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
68
72
|
|
|
73
|
+
-- ─── lt_users ────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
CREATE TABLE IF NOT EXISTS lt_users (
|
|
76
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
77
|
+
external_id TEXT UNIQUE NOT NULL,
|
|
78
|
+
email TEXT,
|
|
79
|
+
display_name TEXT,
|
|
80
|
+
password_hash TEXT,
|
|
81
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended')),
|
|
82
|
+
account_type TEXT NOT NULL DEFAULT 'user' CHECK (account_type IN ('user', 'bot')),
|
|
83
|
+
oauth_provider TEXT,
|
|
84
|
+
oauth_provider_id TEXT,
|
|
85
|
+
metadata JSONB,
|
|
86
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
87
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_lt_users_status ON lt_users (status);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_lt_users_oauth
|
|
92
|
+
ON lt_users (oauth_provider, oauth_provider_id)
|
|
93
|
+
WHERE oauth_provider IS NOT NULL;
|
|
94
|
+
|
|
95
|
+
CREATE TRIGGER lt_users_updated_at
|
|
96
|
+
BEFORE UPDATE ON lt_users
|
|
97
|
+
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
98
|
+
|
|
99
|
+
-- ─── lt_user_roles ───────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS lt_user_roles (
|
|
102
|
+
user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
|
|
103
|
+
role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
104
|
+
type TEXT NOT NULL DEFAULT 'member' CHECK (type IN ('superadmin', 'admin', 'member')),
|
|
105
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
106
|
+
PRIMARY KEY (user_id, role)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_lt_user_roles_type ON lt_user_roles (type);
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_lt_user_roles_user_id ON lt_user_roles (user_id);
|
|
111
|
+
|
|
69
112
|
-- ─── lt_escalations ─────────────────────────────────────────────────────────
|
|
70
113
|
|
|
71
114
|
CREATE TABLE IF NOT EXISTS lt_escalations (
|
|
72
|
-
id
|
|
73
|
-
type
|
|
74
|
-
subtype
|
|
75
|
-
description
|
|
76
|
-
status
|
|
77
|
-
priority
|
|
78
|
-
task_id
|
|
79
|
-
origin_id
|
|
80
|
-
parent_id
|
|
81
|
-
workflow_id
|
|
82
|
-
task_queue
|
|
83
|
-
workflow_type
|
|
84
|
-
role
|
|
85
|
-
assigned_to
|
|
86
|
-
assigned_until
|
|
87
|
-
resolved_at
|
|
88
|
-
claimed_at
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
116
|
+
type TEXT NOT NULL,
|
|
117
|
+
subtype TEXT NOT NULL,
|
|
118
|
+
description TEXT,
|
|
119
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
120
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
121
|
+
task_id UUID REFERENCES lt_tasks(id),
|
|
122
|
+
origin_id TEXT,
|
|
123
|
+
parent_id TEXT,
|
|
124
|
+
workflow_id TEXT,
|
|
125
|
+
task_queue TEXT,
|
|
126
|
+
workflow_type TEXT,
|
|
127
|
+
role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
128
|
+
assigned_to TEXT,
|
|
129
|
+
assigned_until TIMESTAMPTZ,
|
|
130
|
+
resolved_at TIMESTAMPTZ,
|
|
131
|
+
claimed_at TIMESTAMPTZ,
|
|
132
|
+
created_by UUID,
|
|
133
|
+
envelope TEXT NOT NULL,
|
|
134
|
+
metadata JSONB,
|
|
91
135
|
escalation_payload TEXT,
|
|
92
|
-
resolver_payload
|
|
93
|
-
trace_id
|
|
94
|
-
span_id
|
|
95
|
-
created_at
|
|
96
|
-
updated_at
|
|
136
|
+
resolver_payload TEXT,
|
|
137
|
+
trace_id TEXT,
|
|
138
|
+
span_id TEXT,
|
|
139
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
140
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
97
141
|
);
|
|
98
142
|
|
|
99
143
|
CREATE INDEX IF NOT EXISTS idx_lt_escalations_available ON lt_escalations (status, role, assigned_until, created_at DESC);
|
|
@@ -113,44 +157,12 @@ CREATE INDEX IF NOT EXISTS idx_lt_escalations_trace ON lt_escalations (trace_id)
|
|
|
113
157
|
CREATE INDEX IF NOT EXISTS idx_lt_escalations_created_desc ON lt_escalations (created_at DESC);
|
|
114
158
|
CREATE INDEX IF NOT EXISTS idx_lt_escalations_updated_desc ON lt_escalations (updated_at DESC);
|
|
115
159
|
CREATE INDEX IF NOT EXISTS idx_lt_escalations_priority_desc ON lt_escalations (priority DESC, created_at DESC);
|
|
160
|
+
CREATE INDEX IF NOT EXISTS idx_lt_escalations_created_by ON lt_escalations (created_by) WHERE created_by IS NOT NULL;
|
|
116
161
|
|
|
117
162
|
CREATE OR REPLACE TRIGGER trg_lt_escalations_updated_at
|
|
118
163
|
BEFORE UPDATE ON lt_escalations
|
|
119
164
|
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
120
165
|
|
|
121
|
-
-- ─── lt_users ────────────────────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
CREATE TABLE IF NOT EXISTS lt_users (
|
|
124
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
125
|
-
external_id TEXT UNIQUE NOT NULL,
|
|
126
|
-
email TEXT,
|
|
127
|
-
display_name TEXT,
|
|
128
|
-
password_hash TEXT,
|
|
129
|
-
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended')),
|
|
130
|
-
metadata JSONB,
|
|
131
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
132
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
CREATE TRIGGER lt_users_updated_at
|
|
136
|
-
BEFORE UPDATE ON lt_users
|
|
137
|
-
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
138
|
-
|
|
139
|
-
CREATE INDEX IF NOT EXISTS idx_lt_users_status ON lt_users (status);
|
|
140
|
-
|
|
141
|
-
-- ─── lt_user_roles ───────────────────────────────────────────────────────────
|
|
142
|
-
|
|
143
|
-
CREATE TABLE IF NOT EXISTS lt_user_roles (
|
|
144
|
-
user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
|
|
145
|
-
role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
146
|
-
type TEXT NOT NULL DEFAULT 'member' CHECK (type IN ('superadmin', 'admin', 'member')),
|
|
147
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
148
|
-
PRIMARY KEY (user_id, role)
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
CREATE INDEX IF NOT EXISTS idx_lt_user_roles_type ON lt_user_roles (type);
|
|
152
|
-
CREATE INDEX IF NOT EXISTS idx_lt_user_roles_user_id ON lt_user_roles (user_id);
|
|
153
|
-
|
|
154
166
|
-- ─── lt_config_workflows ────────────────────────────────────────────────────
|
|
155
167
|
|
|
156
168
|
CREATE TABLE IF NOT EXISTS lt_config_workflows (
|
|
@@ -193,24 +205,38 @@ CREATE TABLE IF NOT EXISTS lt_config_invocation_roles (
|
|
|
193
205
|
UNIQUE(workflow_type, role)
|
|
194
206
|
);
|
|
195
207
|
|
|
208
|
+
-- ─── lt_config_role_escalations ─────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
CREATE TABLE IF NOT EXISTS lt_config_role_escalations (
|
|
211
|
+
source_role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
212
|
+
target_role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
213
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
214
|
+
PRIMARY KEY (source_role, target_role)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_lt_config_role_escalations_source
|
|
218
|
+
ON lt_config_role_escalations (source_role);
|
|
219
|
+
|
|
196
220
|
-- ─── lt_mcp_servers ─────────────────────────────────────────────────────────
|
|
197
221
|
|
|
198
222
|
CREATE TABLE IF NOT EXISTS lt_mcp_servers (
|
|
199
|
-
id
|
|
200
|
-
name
|
|
201
|
-
description
|
|
202
|
-
transport_type
|
|
203
|
-
transport_config
|
|
204
|
-
auto_connect
|
|
205
|
-
tool_manifest
|
|
206
|
-
status
|
|
207
|
-
|
|
208
|
-
last_connected_at
|
|
209
|
-
metadata
|
|
210
|
-
tags
|
|
211
|
-
compile_hints
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
224
|
+
name TEXT UNIQUE NOT NULL,
|
|
225
|
+
description TEXT,
|
|
226
|
+
transport_type TEXT NOT NULL CHECK (transport_type IN ('stdio', 'sse', 'streamable-http')),
|
|
227
|
+
transport_config JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
228
|
+
auto_connect BOOLEAN NOT NULL DEFAULT false,
|
|
229
|
+
tool_manifest JSONB,
|
|
230
|
+
status TEXT NOT NULL DEFAULT 'registered'
|
|
231
|
+
CHECK (status IN ('registered', 'connected', 'error', 'disconnected')),
|
|
232
|
+
last_connected_at TIMESTAMPTZ,
|
|
233
|
+
metadata JSONB,
|
|
234
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
235
|
+
compile_hints TEXT,
|
|
236
|
+
required_scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
237
|
+
credential_providers TEXT[] NOT NULL DEFAULT '{}',
|
|
238
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
239
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
214
240
|
);
|
|
215
241
|
|
|
216
242
|
CREATE INDEX IF NOT EXISTS idx_lt_mcp_servers_name ON lt_mcp_servers (name);
|
|
@@ -222,18 +248,6 @@ CREATE OR REPLACE TRIGGER trg_lt_mcp_servers_updated_at
|
|
|
222
248
|
BEFORE UPDATE ON lt_mcp_servers
|
|
223
249
|
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
224
250
|
|
|
225
|
-
-- ─── lt_config_role_escalations ─────────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
CREATE TABLE IF NOT EXISTS lt_config_role_escalations (
|
|
228
|
-
source_role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
229
|
-
target_role TEXT NOT NULL REFERENCES lt_roles(role),
|
|
230
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
231
|
-
PRIMARY KEY (source_role, target_role)
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
CREATE INDEX IF NOT EXISTS idx_lt_config_role_escalations_source
|
|
235
|
-
ON lt_config_role_escalations (source_role);
|
|
236
|
-
|
|
237
251
|
-- ─── lt_yaml_workflows ──────────────────────────────────────────────────────
|
|
238
252
|
|
|
239
253
|
CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
|
|
@@ -261,6 +275,12 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
|
|
|
261
275
|
cron_schedule TEXT,
|
|
262
276
|
cron_envelope JSONB,
|
|
263
277
|
execute_as TEXT,
|
|
278
|
+
original_prompt TEXT,
|
|
279
|
+
category TEXT,
|
|
280
|
+
search_vector TSVECTOR,
|
|
281
|
+
set_id UUID,
|
|
282
|
+
set_role TEXT CHECK (set_role IN ('leaf', 'composition', 'router')),
|
|
283
|
+
set_build_order INTEGER,
|
|
264
284
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
265
285
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
266
286
|
);
|
|
@@ -268,11 +288,36 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflows (
|
|
|
268
288
|
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_status ON lt_yaml_workflows (status);
|
|
269
289
|
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_app_id ON lt_yaml_workflows (app_id);
|
|
270
290
|
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_tags ON lt_yaml_workflows USING GIN (tags);
|
|
291
|
+
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_search ON lt_yaml_workflows USING GIN (search_vector);
|
|
292
|
+
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_category ON lt_yaml_workflows (category) WHERE category IS NOT NULL;
|
|
293
|
+
CREATE INDEX IF NOT EXISTS idx_lt_yaml_workflows_set_id ON lt_yaml_workflows (set_id) WHERE set_id IS NOT NULL;
|
|
294
|
+
|
|
295
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_yaml_workflows_app_topic_unique
|
|
296
|
+
ON lt_yaml_workflows (app_id, graph_topic)
|
|
297
|
+
WHERE status != 'archived';
|
|
271
298
|
|
|
272
299
|
CREATE OR REPLACE TRIGGER trg_lt_yaml_workflows_updated_at
|
|
273
300
|
BEFORE UPDATE ON lt_yaml_workflows
|
|
274
301
|
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
275
302
|
|
|
303
|
+
-- Full-text search trigger
|
|
304
|
+
CREATE OR REPLACE FUNCTION lt_yaml_workflows_search_vector_update()
|
|
305
|
+
RETURNS TRIGGER AS $$
|
|
306
|
+
BEGIN
|
|
307
|
+
NEW.search_vector :=
|
|
308
|
+
setweight(to_tsvector('english', coalesce(NEW.name, '')), 'A') ||
|
|
309
|
+
setweight(to_tsvector('english', coalesce(NEW.original_prompt, '')), 'A') ||
|
|
310
|
+
setweight(to_tsvector('english', coalesce(NEW.description, '')), 'B') ||
|
|
311
|
+
setweight(to_tsvector('english', coalesce(NEW.category, '')), 'C') ||
|
|
312
|
+
setweight(to_tsvector('english', coalesce(array_to_string(NEW.tags, ' '), '')), 'C');
|
|
313
|
+
RETURN NEW;
|
|
314
|
+
END;
|
|
315
|
+
$$ LANGUAGE plpgsql;
|
|
316
|
+
|
|
317
|
+
CREATE OR REPLACE TRIGGER trg_lt_yaml_workflows_search_vector
|
|
318
|
+
BEFORE INSERT OR UPDATE ON lt_yaml_workflows
|
|
319
|
+
FOR EACH ROW EXECUTE FUNCTION lt_yaml_workflows_search_vector_update();
|
|
320
|
+
|
|
276
321
|
-- ─── lt_yaml_workflow_versions ──────────────────────────────────────────────
|
|
277
322
|
|
|
278
323
|
CREATE TABLE IF NOT EXISTS lt_yaml_workflow_versions (
|
|
@@ -292,6 +337,136 @@ CREATE TABLE IF NOT EXISTS lt_yaml_workflow_versions (
|
|
|
292
337
|
CREATE INDEX IF NOT EXISTS idx_lt_yaml_wf_versions_workflow
|
|
293
338
|
ON lt_yaml_workflow_versions (workflow_id, version DESC);
|
|
294
339
|
|
|
340
|
+
-- ─── lt_workflow_sets ───────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
CREATE TABLE IF NOT EXISTS lt_workflow_sets (
|
|
343
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
344
|
+
name TEXT UNIQUE NOT NULL,
|
|
345
|
+
description TEXT,
|
|
346
|
+
specification TEXT NOT NULL,
|
|
347
|
+
plan JSONB NOT NULL DEFAULT '[]'::JSONB,
|
|
348
|
+
namespaces TEXT[] NOT NULL DEFAULT '{}',
|
|
349
|
+
status TEXT NOT NULL DEFAULT 'planning'
|
|
350
|
+
CHECK (status IN ('planning','planned','building','deploying','completed','failed')),
|
|
351
|
+
source_workflow_id TEXT,
|
|
352
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
353
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
CREATE OR REPLACE TRIGGER trg_lt_workflow_sets_updated_at
|
|
357
|
+
BEFORE UPDATE ON lt_workflow_sets
|
|
358
|
+
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
359
|
+
|
|
360
|
+
-- Add FK for lt_yaml_workflows.set_id (table already exists, add constraint)
|
|
361
|
+
DO $$ BEGIN
|
|
362
|
+
ALTER TABLE lt_yaml_workflows ADD CONSTRAINT fk_lt_yaml_workflows_set_id
|
|
363
|
+
FOREIGN KEY (set_id) REFERENCES lt_workflow_sets(id) ON DELETE SET NULL;
|
|
364
|
+
EXCEPTION
|
|
365
|
+
WHEN duplicate_object THEN NULL;
|
|
366
|
+
END $$;
|
|
367
|
+
|
|
368
|
+
-- ─── lt_oauth_tokens ────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
CREATE TABLE IF NOT EXISTS lt_oauth_tokens (
|
|
371
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
372
|
+
user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
|
|
373
|
+
provider TEXT NOT NULL,
|
|
374
|
+
label TEXT NOT NULL DEFAULT 'default',
|
|
375
|
+
access_token_enc TEXT NOT NULL,
|
|
376
|
+
refresh_token_enc TEXT,
|
|
377
|
+
token_type TEXT NOT NULL DEFAULT 'bearer',
|
|
378
|
+
scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
379
|
+
expires_at TIMESTAMPTZ,
|
|
380
|
+
provider_user_id TEXT NOT NULL,
|
|
381
|
+
provider_email TEXT,
|
|
382
|
+
metadata JSONB,
|
|
383
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
384
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
385
|
+
UNIQUE (user_id, provider, label)
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
CREATE INDEX IF NOT EXISTS idx_lt_oauth_tokens_provider
|
|
389
|
+
ON lt_oauth_tokens (provider, user_id);
|
|
390
|
+
|
|
391
|
+
CREATE OR REPLACE TRIGGER trg_lt_oauth_tokens_updated_at
|
|
392
|
+
BEFORE UPDATE ON lt_oauth_tokens
|
|
393
|
+
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
394
|
+
|
|
395
|
+
-- ─── lt_service_tokens ──────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
CREATE TABLE IF NOT EXISTS lt_service_tokens (
|
|
398
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
399
|
+
name TEXT UNIQUE NOT NULL,
|
|
400
|
+
token_hash TEXT NOT NULL,
|
|
401
|
+
server_id UUID REFERENCES lt_mcp_servers(id) ON DELETE CASCADE,
|
|
402
|
+
scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
403
|
+
expires_at TIMESTAMPTZ,
|
|
404
|
+
last_used_at TIMESTAMPTZ,
|
|
405
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
406
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
CREATE INDEX IF NOT EXISTS idx_lt_service_tokens_server
|
|
410
|
+
ON lt_service_tokens (server_id);
|
|
411
|
+
|
|
412
|
+
CREATE OR REPLACE TRIGGER trg_lt_service_tokens_updated_at
|
|
413
|
+
BEFORE UPDATE ON lt_service_tokens
|
|
414
|
+
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
415
|
+
|
|
416
|
+
-- ─── lt_bot_api_keys ────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
CREATE TABLE IF NOT EXISTS lt_bot_api_keys (
|
|
419
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
420
|
+
name TEXT NOT NULL,
|
|
421
|
+
user_id UUID NOT NULL REFERENCES lt_users(id) ON DELETE CASCADE,
|
|
422
|
+
key_hash TEXT NOT NULL,
|
|
423
|
+
scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
424
|
+
expires_at TIMESTAMPTZ,
|
|
425
|
+
last_used_at TIMESTAMPTZ,
|
|
426
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
427
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
428
|
+
UNIQUE (user_id, name)
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
CREATE INDEX IF NOT EXISTS idx_bot_api_keys_user_id ON lt_bot_api_keys (user_id);
|
|
432
|
+
|
|
433
|
+
-- ─── lt_ephemeral_credentials ───────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
CREATE TABLE IF NOT EXISTS lt_ephemeral_credentials (
|
|
436
|
+
token UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
437
|
+
value BYTEA NOT NULL,
|
|
438
|
+
label TEXT,
|
|
439
|
+
max_uses INTEGER NOT NULL DEFAULT 0,
|
|
440
|
+
use_count INTEGER NOT NULL DEFAULT 0,
|
|
441
|
+
expires_at TIMESTAMPTZ,
|
|
442
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
CREATE INDEX IF NOT EXISTS idx_lt_ephemeral_expiry
|
|
446
|
+
ON lt_ephemeral_credentials (expires_at)
|
|
447
|
+
WHERE expires_at IS NOT NULL;
|
|
448
|
+
|
|
449
|
+
-- ─── lt_knowledge ───────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
CREATE TABLE IF NOT EXISTS lt_knowledge (
|
|
452
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
453
|
+
domain TEXT NOT NULL,
|
|
454
|
+
key TEXT NOT NULL,
|
|
455
|
+
data JSONB NOT NULL DEFAULT '{}',
|
|
456
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
457
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
458
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
459
|
+
UNIQUE(domain, key)
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
CREATE INDEX IF NOT EXISTS idx_lt_knowledge_domain ON lt_knowledge (domain);
|
|
463
|
+
CREATE INDEX IF NOT EXISTS idx_lt_knowledge_tags ON lt_knowledge USING GIN (tags);
|
|
464
|
+
CREATE INDEX IF NOT EXISTS idx_lt_knowledge_data ON lt_knowledge USING GIN (data);
|
|
465
|
+
|
|
466
|
+
CREATE TRIGGER lt_knowledge_updated_at
|
|
467
|
+
BEFORE UPDATE ON lt_knowledge
|
|
468
|
+
FOR EACH ROW EXECUTE FUNCTION lt_set_updated_at();
|
|
469
|
+
|
|
295
470
|
-- ─── lt_namespaces ──────────────────────────────────────────────────────────
|
|
296
471
|
|
|
297
472
|
CREATE TABLE IF NOT EXISTS lt_namespaces (
|