spine-framework 0.3.108 → 0.3.110

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,176 @@
1
+ -- Migration 027: Embedding lifecycle — approved_at, revoked_at, content_hash
2
+ --
3
+ -- Adds three columns to the embeddings table so that:
4
+ -- 1. Only approved embeddings are returned by match_embeddings (hard filter, not prompt-based).
5
+ -- 2. Embeddings are invalidated automatically when their source item is unpublished or deleted.
6
+ -- 3. Re-embedding is skipped when content has not changed (content_hash deduplication).
7
+ --
8
+ -- Adds filter_item_types parameter to match_embeddings RPC so agents can restrict
9
+ -- retrieval to specific content types (kb_article, code_chunk, db_schema, etc.) at
10
+ -- the DB layer rather than relying on post-processing.
11
+ --
12
+ -- Adds helper functions revoke_embeddings_for_item and approve_embeddings_for_item
13
+ -- called by the items_embedding_sync trigger.
14
+ --
15
+ -- IMPORTANT: approved_at defaults to now() so all existing embeddings are treated
16
+ -- as approved. revoked_at defaults to NULL (not revoked).
17
+
18
+ -- ============================================
19
+ -- COLUMNS
20
+ -- ============================================
21
+
22
+ ALTER TABLE public.embeddings
23
+ ADD COLUMN IF NOT EXISTS approved_at timestamptz DEFAULT now(),
24
+ ADD COLUMN IF NOT EXISTS revoked_at timestamptz DEFAULT NULL,
25
+ ADD COLUMN IF NOT EXISTS content_hash text DEFAULT NULL;
26
+
27
+ -- Partial index for fast active-embedding lookups (approved and not revoked)
28
+ CREATE INDEX IF NOT EXISTS embeddings_active_idx
29
+ ON public.embeddings (account_id, document_id)
30
+ WHERE approved_at IS NOT NULL AND revoked_at IS NULL;
31
+
32
+ -- ============================================
33
+ -- HELPER FUNCTIONS
34
+ -- ============================================
35
+
36
+ -- Revoke all embeddings for a given item (called when item leaves published state)
37
+ CREATE OR REPLACE FUNCTION public.revoke_embeddings_for_item(p_item_id uuid)
38
+ RETURNS integer
39
+ LANGUAGE plpgsql
40
+ SECURITY DEFINER
41
+ SET search_path = public
42
+ AS $$
43
+ DECLARE
44
+ v_count integer;
45
+ BEGIN
46
+ UPDATE public.embeddings
47
+ SET revoked_at = now()
48
+ WHERE document_id = p_item_id::text
49
+ AND revoked_at IS NULL;
50
+
51
+ GET DIAGNOSTICS v_count = ROW_COUNT;
52
+ RETURN v_count;
53
+ END;
54
+ $$;
55
+
56
+ -- Approve all embeddings for a given item (called when item re-enters published state)
57
+ CREATE OR REPLACE FUNCTION public.approve_embeddings_for_item(p_item_id uuid)
58
+ RETURNS integer
59
+ LANGUAGE plpgsql
60
+ SECURITY DEFINER
61
+ SET search_path = public
62
+ AS $$
63
+ DECLARE
64
+ v_count integer;
65
+ BEGIN
66
+ UPDATE public.embeddings
67
+ SET approved_at = now(),
68
+ revoked_at = NULL
69
+ WHERE document_id = p_item_id::text;
70
+
71
+ GET DIAGNOSTICS v_count = ROW_COUNT;
72
+ RETURN v_count;
73
+ END;
74
+ $$;
75
+
76
+ GRANT EXECUTE ON FUNCTION public.revoke_embeddings_for_item(uuid) TO service_role;
77
+ GRANT EXECUTE ON FUNCTION public.approve_embeddings_for_item(uuid) TO service_role;
78
+
79
+ -- ============================================
80
+ -- ITEMS → EMBEDDINGS SYNC TRIGGER
81
+ -- ============================================
82
+ -- When an item's status changes, keep its embeddings in sync automatically.
83
+ -- published → approve (clear revoked_at, set approved_at)
84
+ -- anything → revoke (set revoked_at, leave approved_at intact for audit)
85
+
86
+ CREATE OR REPLACE FUNCTION public.sync_item_embeddings()
87
+ RETURNS TRIGGER
88
+ LANGUAGE plpgsql
89
+ SECURITY DEFINER
90
+ SET search_path = public
91
+ AS $$
92
+ BEGIN
93
+ -- Only act on status changes
94
+ IF OLD.status IS NOT DISTINCT FROM NEW.status THEN
95
+ RETURN NEW;
96
+ END IF;
97
+
98
+ IF NEW.status = 'published' THEN
99
+ PERFORM public.approve_embeddings_for_item(NEW.id);
100
+ ELSE
101
+ PERFORM public.revoke_embeddings_for_item(NEW.id);
102
+ END IF;
103
+
104
+ RETURN NEW;
105
+ END;
106
+ $$;
107
+
108
+ DROP TRIGGER IF EXISTS items_embedding_sync ON public.items;
109
+ CREATE TRIGGER items_embedding_sync
110
+ AFTER UPDATE OF status ON public.items
111
+ FOR EACH ROW
112
+ EXECUTE FUNCTION public.sync_item_embeddings();
113
+
114
+ -- ============================================
115
+ -- UPDATED match_embeddings RPC
116
+ -- ============================================
117
+ -- Adds filter_item_types parameter and hard approved_at / revoked_at filter.
118
+ -- Backward compatible: existing callers that omit filter_item_types get the same
119
+ -- behaviour as before, except that revoked / unapproved embeddings are now excluded.
120
+
121
+ CREATE OR REPLACE FUNCTION public.match_embeddings(
122
+ query_embedding vector(1536),
123
+ match_count int DEFAULT 10,
124
+ filter_account_ids uuid[] DEFAULT NULL,
125
+ filter_vector_type text DEFAULT NULL,
126
+ similarity_threshold float DEFAULT 0.15,
127
+ filter_item_types text[] DEFAULT NULL
128
+ )
129
+ RETURNS TABLE (
130
+ id uuid,
131
+ document_id text,
132
+ content text,
133
+ metadata jsonb,
134
+ account_id uuid,
135
+ similarity float
136
+ )
137
+ LANGUAGE plpgsql
138
+ SECURITY DEFINER
139
+ SET search_path = public
140
+ AS $$
141
+ BEGIN
142
+ RETURN QUERY
143
+ SELECT
144
+ e.id,
145
+ e.document_id,
146
+ e.content,
147
+ e.metadata,
148
+ e.account_id,
149
+ 1 - (e.embedding <=> query_embedding) AS similarity
150
+ FROM public.embeddings e
151
+ WHERE
152
+ -- ── Hard lifecycle filter — never negotiable ──────────────────────────
153
+ e.approved_at IS NOT NULL
154
+ AND e.revoked_at IS NULL
155
+
156
+ -- ── Account scope (NULL = no account filter — platform-wide search) ───
157
+ AND (filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
158
+
159
+ -- ── Vector type (NULL = all types) ────────────────────────────────────
160
+ AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
161
+
162
+ -- ── Item type (NULL = all item types) ─────────────────────────────────
163
+ AND (filter_item_types IS NULL OR e.metadata->>'item_type' = ANY(filter_item_types))
164
+
165
+ -- ── Similarity gate ───────────────────────────────────────────────────
166
+ AND (1 - (e.embedding <=> query_embedding)) >= similarity_threshold
167
+
168
+ ORDER BY e.embedding <=> query_embedding
169
+ LIMIT match_count;
170
+ END;
171
+ $$;
172
+
173
+ -- Re-grant execute to all roles (replace = drop+create resets grants)
174
+ GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float, text[]) TO authenticated;
175
+ GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float, text[]) TO service_role;
176
+ GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float, text[]) TO anon;
@@ -0,0 +1,49 @@
1
+ -- Migration 028: Add AI metadata fields to support_reply/community_reply design_schema
2
+ --
3
+ -- Existing message rows were stamped with the old design_schema, which did not
4
+ -- declare permissions for AI metadata fields (message_type, confidence, retrieved_sources,
5
+ -- feedback, etc.). The permission sanitizer therefore stripped those fields from API
6
+ -- responses, hiding the AI label, confidence badge, sources, and feedback UI.
7
+ --
8
+ -- This migration updates both the type definitions and the stamped design_schema on
9
+ -- every existing message record so the new fields are readable (and feedback writable
10
+ -- by members) without loosening the permission model.
11
+
12
+ DO $$
13
+ DECLARE
14
+ ai_fields jsonb := '{
15
+ "message_type": { "data_type": "text", "label": "Message Type", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
16
+ "confidence": { "data_type": "number", "label": "AI Confidence", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
17
+ "retrieval_confidence": { "data_type": "number", "label": "Retrieval Confidence", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
18
+ "retrieved_sources": { "data_type": "json", "label": "Retrieved Sources", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
19
+ "agent_id": { "data_type": "text", "label": "Agent ID", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
20
+ "prompt_config_id": { "data_type": "text", "label": "Prompt Config ID", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
21
+ "sender_type": { "data_type": "text", "label": "Sender Type", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
22
+ "feedback": { "data_type": "text", "label": "Feedback", "system": true, "permissions": { "system_admin": ["read", "write"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read", "write"], "member": ["read", "write"] } },
23
+ "feedback_at": { "data_type": "datetime", "label": "Feedback At", "system": true, "permissions": { "system_admin": ["read", "write"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read", "write"], "member": ["read", "write"] } },
24
+ "escalated": { "data_type": "boolean", "label": "Escalated", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
25
+ "suggested_title": { "data_type": "text", "label": "Suggested Title", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } },
26
+ "tool_calls": { "data_type": "json", "label": "Tool Calls", "system": true, "permissions": { "system_admin": ["read"], "support_admin": ["read"], "support": ["read"], "member_admin": ["read"], "member": ["read"] } }
27
+ }'::jsonb;
28
+ BEGIN
29
+ -- Update type definitions
30
+ UPDATE types
31
+ SET design_schema = jsonb_set(
32
+ design_schema,
33
+ '{fields}',
34
+ COALESCE(design_schema->'fields', '{}'::jsonb) || ai_fields
35
+ )
36
+ WHERE slug IN ('support_reply', 'community_reply')
37
+ AND kind = 'message';
38
+
39
+ -- Update stamped design_schema on existing messages
40
+ UPDATE messages
41
+ SET design_schema = jsonb_set(
42
+ design_schema,
43
+ '{fields}',
44
+ COALESCE(design_schema->'fields', '{}'::jsonb) || ai_fields
45
+ )
46
+ WHERE type_id IN (
47
+ SELECT id FROM types WHERE slug IN ('support_reply', 'community_reply') AND kind = 'message'
48
+ );
49
+ END $$;
@@ -0,0 +1,41 @@
1
+ -- Migration 029: Tighten the support_triage prompt so the AI escalates when no relevant knowledge is retrieved.
2
+ --
3
+ -- The previous prompt told the agent to "respond helpfully and accurately" without
4
+ -- explicitly forbidding general-knowledge answers or clarifying questions. This led to
5
+ -- the AI asking questions even when the knowledge base was empty. This migration
6
+ -- updates both the prompt_config and the agent's system_prompt to enforce:
7
+ -- 1. answer only from retrieved knowledge
8
+ -- 2. escalate immediately when no relevant knowledge is retrieved
9
+ -- 3. never ask the customer clarifying questions
10
+
11
+ UPDATE prompt_configs
12
+ SET context_template = 'A customer has submitted the following support request:
13
+
14
+ {{user_message}}
15
+
16
+ You may only answer using the relevant information provided in this prompt under ''## Relevant Information''.
17
+
18
+ Rules:
19
+ - If no relevant information is provided, or if it is insufficient to answer confidently, you MUST escalate to a human agent.
20
+ - Do not ask the customer clarifying questions.
21
+ - Do not answer from general knowledge or make up information.
22
+ - Be concise, accurate, and empathetic.
23
+
24
+ If you escalate, write a brief message telling the customer that a human support agent will review their request and follow up shortly.',
25
+ output_mode = 'text',
26
+ updated_at = now()
27
+ WHERE slug = 'support_triage';
28
+
29
+ UPDATE ai_agents
30
+ SET system_prompt = 'You are a first-line support agent for a software platform. Your goal is to resolve customer issues accurately and efficiently on the first response, using only the knowledge retrieved for you.
31
+
32
+ Guidelines:
33
+ - Be concise and clear. Customers want answers, not essays.
34
+ - You may only answer using the retrieved knowledge base articles and prior cases provided in your context.
35
+ - If no relevant knowledge is retrieved, or if it is insufficient to answer confidently, escalate to a human agent immediately.
36
+ - Do not ask the customer clarifying questions.
37
+ - Never fabricate feature behaviour, make promises about the product, or answer from general knowledge.
38
+ - If you escalate, tell the customer that a human support agent will review their request and follow up shortly.
39
+ - Tone: professional but warm. Match the customer''s level of formality.',
40
+ updated_at = now()
41
+ WHERE name = 'Support Triage Agent';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.108",
3
+ "version": "0.3.110",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,6 +33,9 @@
33
33
  ".framework/migrations/024_messages_account_id.sql",
34
34
  ".framework/migrations/025_retype_threads_messages.sql",
35
35
  ".framework/migrations/026_fix_rls_policies.sql",
36
+ ".framework/migrations/027_embedding_lifecycle.sql",
37
+ ".framework/migrations/028_fix_message_data_permissions.sql",
38
+ ".framework/migrations/029_tighten_support_triage_prompt.sql",
36
39
  ".framework/public/",
37
40
  ".framework/scripts/",
38
41
  ".framework/seeds/",