spine-framework 0.3.104 → 0.3.106
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/.framework/functions/_shared/agent-runner.ts +160 -43
- package/.framework/migrations/001_schema.sql +1 -0
- package/.framework/migrations/022_route_prefix_domain_unique.sql +21 -0
- package/.framework/migrations/023_match_embeddings_rpc.sql +78 -0
- package/.framework/migrations/024_messages_account_id.sql +9 -0
- package/LICENSE.md +93 -63
- package/docs/README.md +96 -130
- package/package.json +4 -1
- package/scripts/boundary-check.sh +6 -2
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { CoreContext } from './middleware'
|
|
46
46
|
import { adminDb } from './db'
|
|
47
47
|
import { emitAudit } from './audit'
|
|
48
|
+
import { create as createRecord } from '../admin-data'
|
|
48
49
|
|
|
49
50
|
// ─── TYPES ───────────────────────────────────────────────────────────────
|
|
50
51
|
|
|
@@ -472,16 +473,28 @@ async function buildContext(
|
|
|
472
473
|
/**
|
|
473
474
|
* Retrieves relevant documents from the `embeddings` table for RAG.
|
|
474
475
|
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
476
|
+
* Strategy (tiered):
|
|
477
|
+
* 1. Generate a query embedding via the OpenAI embeddings API.
|
|
478
|
+
* 2. Call the `match_embeddings` Postgres RPC (vector cosine similarity).
|
|
479
|
+
* 3. Deduplicate by document_id — keep best-matching chunk per article.
|
|
480
|
+
* 4. Enrich with item titles from the `items` table.
|
|
481
|
+
* 5. Fall back to full-text search if the embedding API is unavailable or
|
|
482
|
+
* the RPC call fails (guarantees graceful degradation in dev/test).
|
|
478
483
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
484
|
+
* Account scoping:
|
|
485
|
+
* - Always includes the requesting account's embeddings.
|
|
486
|
+
* - Always includes the platform KB account (spine-system), excluding
|
|
487
|
+
* rows where metadata.security_level = 'restricted'.
|
|
488
|
+
* - `knowledgeSources` optionally narrows to specific document UUIDs
|
|
489
|
+
* (prompt_config.knowledge_sources); omit or pass [] for full scope.
|
|
490
|
+
*
|
|
491
|
+
* @param query - User message text to embed and search
|
|
492
|
+
* @param knowledgeSources - Optional document UUIDs to restrict search scope
|
|
493
|
+
* @param accountId - Requesting account for scoped access
|
|
494
|
+
* @returns Promise<any[]> — enriched result rows (id, title, content, similarity)
|
|
495
|
+
* @throws never — falls back to text search or [] on any failure
|
|
496
|
+
* @sideEffects HTTP call to OpenAI embeddings API (if API key present)
|
|
497
|
+
* @sideEffects DB read: match_embeddings RPC + items table
|
|
485
498
|
* @calledBy buildContext (if promptConfig.knowledge_sources is non-empty)
|
|
486
499
|
*/
|
|
487
500
|
async function retrieveKnowledge(
|
|
@@ -489,27 +502,113 @@ async function retrieveKnowledge(
|
|
|
489
502
|
knowledgeSources: string[],
|
|
490
503
|
accountId: string
|
|
491
504
|
): Promise<any[]> {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// For now, we'll do a text search fallback
|
|
505
|
+
const SIMILARITY_THRESHOLD = 0.15
|
|
506
|
+
const MATCH_COUNT = 8
|
|
495
507
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
.
|
|
499
|
-
.
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
508
|
+
// ── Tier 1: vector similarity via match_embeddings RPC ──────────────────
|
|
509
|
+
try {
|
|
510
|
+
const apiKey = process.env.OPENAI_API_KEY || process.env.LLM_API_KEY
|
|
511
|
+
const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
|
|
512
|
+
|
|
513
|
+
if (!apiKey) throw new Error('no_api_key')
|
|
514
|
+
|
|
515
|
+
// Resolve platform account ID at runtime — no hardcoded UUIDs
|
|
516
|
+
const { data: platformAccount } = await adminDb
|
|
517
|
+
.from('accounts')
|
|
518
|
+
.select('id')
|
|
519
|
+
.eq('slug', 'spine-system')
|
|
520
|
+
.maybeSingle()
|
|
521
|
+
|
|
522
|
+
const accountIds: string[] = [accountId]
|
|
523
|
+
if (platformAccount?.id && platformAccount.id !== accountId) {
|
|
524
|
+
accountIds.push(platformAccount.id)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Generate query embedding
|
|
528
|
+
const embedRes = await fetch(`${baseUrl}/embeddings`, {
|
|
529
|
+
method: 'POST',
|
|
530
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
531
|
+
body: JSON.stringify({ model: 'text-embedding-3-small', input: query }),
|
|
504
532
|
})
|
|
505
|
-
.
|
|
533
|
+
if (!embedRes.ok) throw new Error(`embed_api_${embedRes.status}`)
|
|
534
|
+
const embedJson: any = await embedRes.json()
|
|
535
|
+
const queryEmbedding: number[] = embedJson.data?.[0]?.embedding || []
|
|
536
|
+
if (!queryEmbedding.length) throw new Error('empty_embedding')
|
|
537
|
+
|
|
538
|
+
// Call vector RPC
|
|
539
|
+
const { data: matches, error: rpcError } = await adminDb.rpc('match_embeddings', {
|
|
540
|
+
query_embedding: `[${queryEmbedding.join(',')}]`,
|
|
541
|
+
match_count: MATCH_COUNT * 3,
|
|
542
|
+
filter_account_ids: accountIds,
|
|
543
|
+
filter_vector_type: 'semantic',
|
|
544
|
+
similarity_threshold: SIMILARITY_THRESHOLD,
|
|
545
|
+
})
|
|
546
|
+
if (rpcError) throw new Error(`rpc_${rpcError.message}`)
|
|
506
547
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
548
|
+
// Filter: security_level restriction for cross-account results
|
|
549
|
+
const filtered = (matches || []).filter((r: any) => {
|
|
550
|
+
if (r.similarity < SIMILARITY_THRESHOLD) return false
|
|
551
|
+
if (r.account_id === accountId) return true
|
|
552
|
+
return r.metadata?.security_level !== 'restricted'
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
// Optionally narrow to knowledgeSources doc IDs
|
|
556
|
+
const scoped = knowledgeSources.length > 0
|
|
557
|
+
? filtered.filter((r: any) => knowledgeSources.includes(r.document_id))
|
|
558
|
+
: filtered
|
|
511
559
|
|
|
512
|
-
|
|
560
|
+
// Deduplicate — best chunk per document
|
|
561
|
+
const bestByDoc = new Map<string, any>()
|
|
562
|
+
for (const r of scoped) {
|
|
563
|
+
const existing = bestByDoc.get(r.document_id)
|
|
564
|
+
if (!existing || r.similarity > existing.similarity) bestByDoc.set(r.document_id, r)
|
|
565
|
+
}
|
|
566
|
+
const deduped = [...bestByDoc.values()]
|
|
567
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
568
|
+
.slice(0, MATCH_COUNT)
|
|
569
|
+
|
|
570
|
+
if (deduped.length === 0) return []
|
|
571
|
+
|
|
572
|
+
// Enrich with item titles
|
|
573
|
+
const { data: items } = await adminDb
|
|
574
|
+
.from('items')
|
|
575
|
+
.select('id, title, description')
|
|
576
|
+
.in('id', deduped.map((r: any) => r.document_id))
|
|
577
|
+
|
|
578
|
+
const itemMap = new Map((items || []).map((i: any) => [i.id, i]))
|
|
579
|
+
return deduped.map((r: any) => ({
|
|
580
|
+
document_id: r.document_id,
|
|
581
|
+
content: r.content,
|
|
582
|
+
metadata: r.metadata,
|
|
583
|
+
similarity: r.similarity,
|
|
584
|
+
title: itemMap.get(r.document_id)?.title || '',
|
|
585
|
+
}))
|
|
586
|
+
|
|
587
|
+
} catch (vectorErr: any) {
|
|
588
|
+
// ── Tier 2: full-text fallback ──────────────────────────────────────
|
|
589
|
+
if (vectorErr.message !== 'no_api_key') {
|
|
590
|
+
console.warn('retrieveKnowledge vector search failed, falling back to text search:', vectorErr.message)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const baseQuery = adminDb
|
|
594
|
+
.from('embeddings')
|
|
595
|
+
.select('document_id, content, metadata, account_id')
|
|
596
|
+
.eq('account_id', accountId)
|
|
597
|
+
.eq('metadata->>vector_type', 'semantic')
|
|
598
|
+
.textSearch('content', query, { type: 'websearch', config: 'english' })
|
|
599
|
+
.limit(5)
|
|
600
|
+
|
|
601
|
+
const q = knowledgeSources.length > 0
|
|
602
|
+
? baseQuery.in('document_id', knowledgeSources)
|
|
603
|
+
: baseQuery
|
|
604
|
+
|
|
605
|
+
const { data: docs, error } = await q
|
|
606
|
+
if (error) {
|
|
607
|
+
console.error('Knowledge retrieval fallback failed:', error)
|
|
608
|
+
return []
|
|
609
|
+
}
|
|
610
|
+
return docs || []
|
|
611
|
+
}
|
|
513
612
|
}
|
|
514
613
|
|
|
515
614
|
/**
|
|
@@ -1068,6 +1167,17 @@ async function handleEscalation(
|
|
|
1068
1167
|
* @sideEffects DB write: messages table
|
|
1069
1168
|
* @calledBy runAgent (user message + agent response)
|
|
1070
1169
|
*/
|
|
1170
|
+
// Module-level cache so we only look up the message type_id once per cold-start
|
|
1171
|
+
let _messageTypeId: string | null = null
|
|
1172
|
+
|
|
1173
|
+
async function getMessageTypeId(): Promise<string> {
|
|
1174
|
+
if (_messageTypeId) return _messageTypeId
|
|
1175
|
+
const { data } = await adminDb.from('types').select('id').eq('slug', 'message').maybeSingle()
|
|
1176
|
+
if (!data?.id) throw new Error('Type "message" not found in types table')
|
|
1177
|
+
_messageTypeId = data.id
|
|
1178
|
+
return _messageTypeId
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1071
1181
|
async function saveMessage(
|
|
1072
1182
|
threadId: string,
|
|
1073
1183
|
content: string,
|
|
@@ -1075,23 +1185,30 @@ async function saveMessage(
|
|
|
1075
1185
|
data: any,
|
|
1076
1186
|
ctx: CoreContext
|
|
1077
1187
|
): Promise<any> {
|
|
1078
|
-
const {
|
|
1079
|
-
|
|
1080
|
-
.
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1188
|
+
const [typeId, { count }] = await Promise.all([
|
|
1189
|
+
getMessageTypeId(),
|
|
1190
|
+
adminDb.from('messages').select('id', { count: 'exact', head: true }).eq('thread_id', threadId),
|
|
1191
|
+
])
|
|
1192
|
+
const sequence = (count ?? 0) + 1
|
|
1193
|
+
const direction = messageType === 'human' ? 'inbound' : 'outbound'
|
|
1194
|
+
|
|
1195
|
+
// Use admin-data's create() so design_schema + validation_schema are stamped
|
|
1196
|
+
// from the type record — ensuring the permission engine returns full rows.
|
|
1197
|
+
const message = await createRecord(ctx, {
|
|
1198
|
+
entity: 'messages',
|
|
1199
|
+
type_id: typeId,
|
|
1200
|
+
thread_id: threadId,
|
|
1201
|
+
person_id: messageType === 'human' ? ctx.principal?.id : null,
|
|
1202
|
+
content,
|
|
1203
|
+
direction,
|
|
1204
|
+
sequence,
|
|
1205
|
+
visibility: 'internal',
|
|
1206
|
+
data: {
|
|
1207
|
+
message_type: messageType,
|
|
1208
|
+
...data
|
|
1209
|
+
},
|
|
1210
|
+
})
|
|
1094
1211
|
|
|
1095
|
-
if (
|
|
1212
|
+
if (!message?.id) throw new Error('Failed to save message')
|
|
1096
1213
|
return message
|
|
1097
1214
|
}
|
|
@@ -105,6 +105,7 @@ CREATE TABLE IF NOT EXISTS public.threads (
|
|
|
105
105
|
CREATE TABLE IF NOT EXISTS public.messages (
|
|
106
106
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
107
107
|
type_id uuid NOT NULL, thread_id uuid NOT NULL,
|
|
108
|
+
account_id uuid REFERENCES public.accounts(id),
|
|
108
109
|
content text NOT NULL, direction text DEFAULT 'outbound',
|
|
109
110
|
sequence integer NOT NULL, visibility text DEFAULT 'internal',
|
|
110
111
|
data jsonb DEFAULT '{}', is_active boolean DEFAULT true,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Migration 022: Allow multiple apps to share a route_prefix when they have
|
|
2
|
+
-- different prod_domains (i.e. they are served from different subdomains).
|
|
3
|
+
--
|
|
4
|
+
-- Before: UNIQUE(route_prefix) WHERE is_active AND route_prefix IS NOT NULL
|
|
5
|
+
-- → only one app could claim "/" across the entire database
|
|
6
|
+
--
|
|
7
|
+
-- After: UNIQUE(route_prefix, COALESCE(prod_domain, '')) WHERE is_active AND route_prefix IS NOT NULL
|
|
8
|
+
-- → two apps can both use "/" as long as they have different prod_domains
|
|
9
|
+
-- → apps with no prod_domain still get a unique slot (COALESCE maps NULL → '')
|
|
10
|
+
--
|
|
11
|
+
-- This is required to support multiple domain-scoped apps (e.g. Cortex on
|
|
12
|
+
-- cortex.spine-framework.com and Portal on portal.spine-framework.com) that
|
|
13
|
+
-- each serve from the root path "/" of their respective domains.
|
|
14
|
+
|
|
15
|
+
-- Drop the old single-column unique index
|
|
16
|
+
DROP INDEX IF EXISTS idx_apps_route_prefix;
|
|
17
|
+
|
|
18
|
+
-- Create the new composite unique index
|
|
19
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_apps_route_prefix_domain
|
|
20
|
+
ON public.apps(route_prefix, COALESCE(prod_domain, ''))
|
|
21
|
+
WHERE is_active = true AND route_prefix IS NOT NULL;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
-- Migration 023: match_embeddings RPC for vector similarity search
|
|
2
|
+
--
|
|
3
|
+
-- Provides the `match_embeddings` Postgres function used by:
|
|
4
|
+
-- - custom_kb-embeddings.ts (handleSearchEmbeddings)
|
|
5
|
+
-- - agent-runner.ts (retrieveKnowledge — after upgrade)
|
|
6
|
+
--
|
|
7
|
+
-- Requires the `vector` extension (enabled in migration 002).
|
|
8
|
+
-- Uses cosine distance (<=>). Returns rows ordered best-first.
|
|
9
|
+
|
|
10
|
+
-- ============================================
|
|
11
|
+
-- HNSW INDEX (idempotent)
|
|
12
|
+
-- ============================================
|
|
13
|
+
-- Create HNSW index on the embedding column for fast approximate nearest-neighbour search.
|
|
14
|
+
-- Skip if it already exists (Postgres 15+ supports IF NOT EXISTS for indexes).
|
|
15
|
+
DO $$
|
|
16
|
+
BEGIN
|
|
17
|
+
IF NOT EXISTS (
|
|
18
|
+
SELECT 1 FROM pg_indexes
|
|
19
|
+
WHERE schemaname = 'public'
|
|
20
|
+
AND tablename = 'embeddings'
|
|
21
|
+
AND indexname = 'embeddings_embedding_hnsw_idx'
|
|
22
|
+
) THEN
|
|
23
|
+
EXECUTE 'CREATE INDEX embeddings_embedding_hnsw_idx
|
|
24
|
+
ON public.embeddings
|
|
25
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
26
|
+
WITH (m = 16, ef_construction = 64)';
|
|
27
|
+
END IF;
|
|
28
|
+
END
|
|
29
|
+
$$;
|
|
30
|
+
|
|
31
|
+
-- ============================================
|
|
32
|
+
-- match_embeddings RPC
|
|
33
|
+
-- ============================================
|
|
34
|
+
CREATE OR REPLACE FUNCTION public.match_embeddings(
|
|
35
|
+
query_embedding vector(1536),
|
|
36
|
+
match_count int DEFAULT 10,
|
|
37
|
+
filter_account_ids uuid[] DEFAULT NULL,
|
|
38
|
+
filter_vector_type text DEFAULT NULL,
|
|
39
|
+
similarity_threshold float DEFAULT 0.15
|
|
40
|
+
)
|
|
41
|
+
RETURNS TABLE (
|
|
42
|
+
id uuid,
|
|
43
|
+
document_id text,
|
|
44
|
+
content text,
|
|
45
|
+
metadata jsonb,
|
|
46
|
+
account_id uuid,
|
|
47
|
+
similarity float
|
|
48
|
+
)
|
|
49
|
+
LANGUAGE plpgsql
|
|
50
|
+
SECURITY DEFINER
|
|
51
|
+
SET search_path = public
|
|
52
|
+
AS $$
|
|
53
|
+
BEGIN
|
|
54
|
+
RETURN QUERY
|
|
55
|
+
SELECT
|
|
56
|
+
e.id,
|
|
57
|
+
e.document_id,
|
|
58
|
+
e.content,
|
|
59
|
+
e.metadata,
|
|
60
|
+
e.account_id,
|
|
61
|
+
1 - (e.embedding <=> query_embedding) AS similarity
|
|
62
|
+
FROM public.embeddings e
|
|
63
|
+
WHERE
|
|
64
|
+
-- account scope filter (NULL = no filter — platform-wide search)
|
|
65
|
+
(filter_account_ids IS NULL OR e.account_id = ANY(filter_account_ids))
|
|
66
|
+
-- vector type filter (NULL = all types)
|
|
67
|
+
AND (filter_vector_type IS NULL OR e.metadata->>'vector_type' = filter_vector_type)
|
|
68
|
+
-- similarity gate (cosine similarity = 1 - cosine distance)
|
|
69
|
+
AND (1 - (e.embedding <=> query_embedding)) >= similarity_threshold
|
|
70
|
+
ORDER BY e.embedding <=> query_embedding -- ascending distance = descending similarity
|
|
71
|
+
LIMIT match_count;
|
|
72
|
+
END;
|
|
73
|
+
$$;
|
|
74
|
+
|
|
75
|
+
-- Grant execute to authenticated and service roles
|
|
76
|
+
GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float) TO authenticated;
|
|
77
|
+
GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float) TO service_role;
|
|
78
|
+
GRANT EXECUTE ON FUNCTION public.match_embeddings(vector, int, uuid[], text, float) TO anon;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Migration 024: Add account_id to messages table
|
|
2
|
+
-- messages was the only runtime table missing account_id.
|
|
3
|
+
-- Required by admin-data's create() (which stamps account_id on all runtime records)
|
|
4
|
+
-- and by RLS policies that scope rows to an account.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE public.messages
|
|
7
|
+
ADD COLUMN IF NOT EXISTS account_id uuid REFERENCES public.accounts(id);
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS idx_messages_account_id ON public.messages(account_id);
|
package/LICENSE.md
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
Spine Framework Internal Use License 1.0.0
|
|
2
|
-
|
|
1
|
+
# Spine Framework Internal Use License 1.0.0
|
|
2
|
+
|
|
3
|
+
**Source-available. Free for internal use. Commercial rights reserved.**
|
|
3
4
|
|
|
4
5
|
This license is based on the structure and intent of the PolyForm Internal Use License 1.0.0, but it has been modified for Spine Framework. This is not the unmodified PolyForm Internal Use License.
|
|
5
6
|
|
|
6
|
-
Required Notice
|
|
7
|
+
## Required Notice
|
|
8
|
+
|
|
7
9
|
Required Notice: Copyright © 2026 Dahl Ventures Inc. All rights reserved.
|
|
8
10
|
|
|
9
|
-
1. Acceptance
|
|
11
|
+
## 1. Acceptance
|
|
12
|
+
|
|
10
13
|
In order to receive any license under these terms, you must agree to these terms as both strict obligations and conditions to all licenses granted to you.
|
|
11
14
|
|
|
12
15
|
If you do not agree to these terms, you do not have permission to use, copy, modify, run, install, access, or create works based on Spine Framework.
|
|
13
16
|
|
|
14
|
-
2. Definitions
|
|
15
|
-
|
|
17
|
+
## 2. Definitions
|
|
18
|
+
|
|
19
|
+
**Software** means Spine Framework, including its source code, object code, packages, modules, templates, schemas, migrations, configuration files, documentation, examples, command-line tools, SDKs, application packs, and related materials provided by the licensor.
|
|
16
20
|
|
|
17
|
-
Licensor means the legal owner of Spine Framework.
|
|
21
|
+
**Licensor** means the legal owner of Spine Framework.
|
|
18
22
|
|
|
19
|
-
You means the individual or legal entity receiving the Software.
|
|
23
|
+
**You** means the individual or legal entity receiving the Software.
|
|
20
24
|
|
|
21
|
-
Your Company means the legal entity on whose behalf you use the Software, including that entity's employees and authorized contractors.
|
|
25
|
+
**Your Company** means the legal entity on whose behalf you use the Software, including that entity's employees and authorized contractors.
|
|
22
26
|
|
|
23
|
-
Internal Business Use means use of the Software solely inside Your Company for Your Company's own internal business operations, internal workflows, internal applications, internal data, internal users, and internal customers or accounts.
|
|
27
|
+
**Internal Business Use** means use of the Software solely inside Your Company for Your Company's own internal business operations, internal workflows, internal applications, internal data, internal users, and internal customers or accounts.
|
|
24
28
|
|
|
25
|
-
Third Party means any person or legal entity other than You or Your Company.
|
|
29
|
+
**Third Party** means any person or legal entity other than You or Your Company.
|
|
26
30
|
|
|
27
|
-
Commercial License means a separate written agreement signed by the Licensor that grants rights beyond this license.
|
|
31
|
+
**Commercial License** means a separate written agreement signed by the Licensor that grants rights beyond this license.
|
|
32
|
+
|
|
33
|
+
## 3. Copyright License
|
|
28
34
|
|
|
29
|
-
3. Copyright License
|
|
30
35
|
The Licensor grants You a copyright license to do everything with the Software that would otherwise infringe the Licensor's copyright, but only for Internal Business Use and only as permitted by these terms.
|
|
31
36
|
|
|
32
37
|
You may install, run, access, evaluate, test, configure, and use the Software for Internal Business Use.
|
|
@@ -35,84 +40,99 @@ You may copy the Software only as reasonably necessary for Internal Business Use
|
|
|
35
40
|
|
|
36
41
|
You may not distribute the Software except as expressly allowed in these terms.
|
|
37
42
|
|
|
38
|
-
4. Changes and New Works
|
|
43
|
+
## 4. Changes and New Works
|
|
44
|
+
|
|
39
45
|
The Licensor grants You a copyright license to modify the Software and create new works based on the Software, but only for Internal Business Use.
|
|
40
46
|
|
|
41
47
|
You may create internal applications, extensions, workflows, integrations, automations, schemas, roles, agents, triggers, pipelines, dashboards, reports, and other internal business systems using the Software.
|
|
42
48
|
|
|
43
49
|
You may not distribute, sell, sublicense, transfer, lease, rent, host for others, white-label, resell, or commercially exploit any modified version, derivative work, extension, application pack, template, or implementation of the Software except under a Commercial License.
|
|
44
50
|
|
|
45
|
-
5. Internal Contractors
|
|
51
|
+
## 5. Internal Contractors
|
|
52
|
+
|
|
46
53
|
Your Company may allow employees and contractors to access and use the Software solely on behalf of Your Company and solely for Your Company's Internal Business Use.
|
|
47
54
|
|
|
48
55
|
Contractors do not receive any independent right to use the Software for themselves, their other clients, their own products, or any Third Party.
|
|
49
56
|
|
|
50
57
|
Your Company is responsible for all use of the Software by its employees, contractors, agents, and representatives.
|
|
51
58
|
|
|
52
|
-
6. Your Data and Application-Specific Work
|
|
59
|
+
## 6. Your Data and Application-Specific Work
|
|
60
|
+
|
|
53
61
|
As between You and the Licensor, You own Your Company's data, records, content, prompts, business logic, customer records, internal workflows, and application-specific code that You independently create using the Software.
|
|
54
62
|
|
|
55
63
|
These terms do not give the Licensor ownership of Your Company's data or independently created application-specific business content.
|
|
56
64
|
|
|
57
65
|
However, ownership of Your Company's data or application-specific work does not give You the right to distribute, resell, white-label, host, or commercially exploit the Software or any portion of the Software except as allowed by these terms or a Commercial License.
|
|
58
66
|
|
|
59
|
-
7. Prohibited Uses
|
|
67
|
+
## 7. Prohibited Uses
|
|
68
|
+
|
|
60
69
|
You may not do any of the following without a Commercial License:
|
|
61
70
|
|
|
62
|
-
Sell, resell, sublicense, rent, lease, lend, transfer, assign, publish, or distribute the Software.
|
|
63
|
-
Offer the Software, or any modified version of the Software, to Third Parties as software, infrastructure, a framework, a platform, a service, or a hosted product.
|
|
64
|
-
Offer the Software as SaaS, PaaS, IaaS, framework-as-a-service, backend-as-a-service, workflow-as-a-service, automation-as-a-service, AI-agent platform, managed service, support service, or similar commercial service.
|
|
65
|
-
White-label, private-label, rebrand, or remove Spine Framework identity from the Software in order to provide it to Third Parties.
|
|
66
|
-
Embed, bundle, package, or include the Software in any product, service, appliance, marketplace offering, application pack, starter kit, template library, SDK, CLI, distribution, or managed implementation made available to Third Parties.
|
|
67
|
-
Use the Software to operate, power, support, or provide a product or service for Third Parties where those Third Parties receive access to the Software or to functionality substantially provided by the Software.
|
|
68
|
-
Use the Software to build, operate, or improve a competing framework, platform, marketplace, application generator, agentic software infrastructure product, or commercial development infrastructure product.
|
|
69
|
-
Provide implementation, consulting, hosting, support, customization, training, migration, or managed services for the Software to Third Parties as a paid or commercial offering, except as expressly authorized by the Licensor.
|
|
70
|
-
Remove, alter, obscure, or bypass copyright notices, license notices, attribution, trademarks, branding, license keys, license checks, usage limits, or technical protection measures included in the Software.
|
|
71
|
-
Use the Software in violation of law, regulation, third-party rights, or these terms.
|
|
72
|
-
|
|
71
|
+
1. Sell, resell, sublicense, rent, lease, lend, transfer, assign, publish, or distribute the Software.
|
|
72
|
+
2. Offer the Software, or any modified version of the Software, to Third Parties as software, infrastructure, a framework, a platform, a service, or a hosted product.
|
|
73
|
+
3. Offer the Software as SaaS, PaaS, IaaS, framework-as-a-service, backend-as-a-service, workflow-as-a-service, automation-as-a-service, AI-agent platform, managed service, support service, or similar commercial service.
|
|
74
|
+
4. White-label, private-label, rebrand, or remove Spine Framework identity from the Software in order to provide it to Third Parties.
|
|
75
|
+
5. Embed, bundle, package, or include the Software in any product, service, appliance, marketplace offering, application pack, starter kit, template library, SDK, CLI, distribution, or managed implementation made available to Third Parties.
|
|
76
|
+
6. Use the Software to operate, power, support, or provide a product or service for Third Parties where those Third Parties receive access to the Software or to functionality substantially provided by the Software.
|
|
77
|
+
7. Use the Software to build, operate, or improve a competing framework, platform, marketplace, application generator, agentic software infrastructure product, or commercial development infrastructure product.
|
|
78
|
+
8. Provide implementation, consulting, hosting, support, customization, training, migration, or managed services for the Software to Third Parties as a paid or commercial offering, except as expressly authorized by the Licensor.
|
|
79
|
+
9. Remove, alter, obscure, or bypass copyright notices, license notices, attribution, trademarks, branding, license keys, license checks, usage limits, or technical protection measures included in the Software.
|
|
80
|
+
10. Use the Software in violation of law, regulation, third-party rights, or these terms.
|
|
81
|
+
|
|
82
|
+
## 8. Examples of Permitted Use
|
|
83
|
+
|
|
73
84
|
The following are permitted under this license, provided they are solely for Internal Business Use:
|
|
74
85
|
|
|
75
|
-
A company installs Spine Framework to build its own internal CRM.
|
|
76
|
-
A company uses Spine Framework to operate an internal customer support portal for its own support team and its own customers.
|
|
77
|
-
A company modifies Spine Framework for its internal workflows, roles, permissions, automations, pipelines, and integrations.
|
|
78
|
-
A company lets its employees and authorized contractors configure, maintain, and support its internal Spine Framework installation.
|
|
79
|
-
A company creates internal dashboards, agents, knowledge bases, audit trails, and operational tools on top of Spine Framework.
|
|
80
|
-
|
|
86
|
+
1. A company installs Spine Framework to build its own internal CRM.
|
|
87
|
+
2. A company uses Spine Framework to operate an internal customer support portal for its own support team and its own customers.
|
|
88
|
+
3. A company modifies Spine Framework for its internal workflows, roles, permissions, automations, pipelines, and integrations.
|
|
89
|
+
4. A company lets its employees and authorized contractors configure, maintain, and support its internal Spine Framework installation.
|
|
90
|
+
5. A company creates internal dashboards, agents, knowledge bases, audit trails, and operational tools on top of Spine Framework.
|
|
91
|
+
|
|
92
|
+
## 9. Examples Requiring a Commercial License
|
|
93
|
+
|
|
81
94
|
The following are not permitted under this license and require a Commercial License:
|
|
82
95
|
|
|
83
|
-
Selling Spine Framework or a modified version of Spine Framework.
|
|
84
|
-
Offering Spine Framework as a hosted SaaS product.
|
|
85
|
-
Offering a white-labeled application built on Spine Framework to customers.
|
|
86
|
-
Packaging Spine Framework into a commercial starter kit, template, marketplace app, agency offering, or developer platform.
|
|
87
|
-
Running Spine Framework for multiple clients as a managed service provider, agency, consultant, systems integrator, or outsourcing firm.
|
|
88
|
-
Embedding Spine Framework into a product sold to customers.
|
|
89
|
-
Forking Spine Framework to launch a competing framework, backend, app platform, AI-agent platform, or development infrastructure product.
|
|
90
|
-
Publishing modified Spine Framework source code to a public repository.
|
|
91
|
-
Giving a customer, client, partner, reseller, or affiliate a copy of Spine Framework.
|
|
92
|
-
|
|
96
|
+
1. Selling Spine Framework or a modified version of Spine Framework.
|
|
97
|
+
2. Offering Spine Framework as a hosted SaaS product.
|
|
98
|
+
3. Offering a white-labeled application built on Spine Framework to customers.
|
|
99
|
+
4. Packaging Spine Framework into a commercial starter kit, template, marketplace app, agency offering, or developer platform.
|
|
100
|
+
5. Running Spine Framework for multiple clients as a managed service provider, agency, consultant, systems integrator, or outsourcing firm.
|
|
101
|
+
6. Embedding Spine Framework into a product sold to customers.
|
|
102
|
+
7. Forking Spine Framework to launch a competing framework, backend, app platform, AI-agent platform, or development infrastructure product.
|
|
103
|
+
8. Publishing modified Spine Framework source code to a public repository.
|
|
104
|
+
9. Giving a customer, client, partner, reseller, or affiliate a copy of Spine Framework.
|
|
105
|
+
|
|
106
|
+
## 10. Distribution
|
|
107
|
+
|
|
93
108
|
You may not distribute the Software, modified versions of the Software, or works based on the Software to any Third Party except as expressly permitted by these terms or a Commercial License.
|
|
94
109
|
|
|
95
110
|
You may share copies of the Software with employees and contractors of Your Company only as necessary for Internal Business Use and only if they are bound by obligations consistent with these terms.
|
|
96
111
|
|
|
97
112
|
Any copy of the Software must include this license and all Required Notices.
|
|
98
113
|
|
|
99
|
-
11. Trademarks
|
|
114
|
+
## 11. Trademarks
|
|
115
|
+
|
|
100
116
|
This license does not grant You any right to use the Licensor's names, trademarks, service marks, logos, product names, domain names, or branding except as necessary to identify the Software for Internal Business Use.
|
|
101
117
|
|
|
102
118
|
You may not use Spine Framework branding in a way that suggests sponsorship, endorsement, partnership, certification, resale authorization, or commercial authorization without written permission from the Licensor.
|
|
103
119
|
|
|
104
|
-
12. Patent License
|
|
120
|
+
## 12. Patent License
|
|
121
|
+
|
|
105
122
|
The Licensor grants You a patent license for the Software that covers patent claims the Licensor can license, or later becomes able to license, that You would infringe by using the Software as permitted by these terms.
|
|
106
123
|
|
|
107
|
-
13. Patent Defense
|
|
124
|
+
## 13. Patent Defense
|
|
125
|
+
|
|
108
126
|
If You make any written claim that the Software infringes or contributes to infringement of any patent, Your patent license for the Software ends immediately.
|
|
109
127
|
|
|
110
128
|
If Your Company makes such a claim, Your Company's patent license ends immediately.
|
|
111
129
|
|
|
112
|
-
14. Fair Use and Other Legal Rights
|
|
130
|
+
## 14. Fair Use and Other Legal Rights
|
|
131
|
+
|
|
113
132
|
You may have fair use, fair dealing, or other rights under applicable law. These terms do not limit rights that the law does not allow these terms to limit.
|
|
114
133
|
|
|
115
|
-
15. No Other Rights
|
|
134
|
+
## 15. No Other Rights
|
|
135
|
+
|
|
116
136
|
These terms do not allow You to sublicense, transfer, assign, or delegate any license granted under these terms except as expressly allowed here.
|
|
117
137
|
|
|
118
138
|
These terms do not prevent the Licensor from granting licenses to anyone else.
|
|
@@ -121,43 +141,51 @@ These terms do not imply any license other than the licenses expressly granted h
|
|
|
121
141
|
|
|
122
142
|
All rights not expressly granted are reserved by the Licensor.
|
|
123
143
|
|
|
124
|
-
16. Violations and Cure
|
|
144
|
+
## 16. Violations and Cure
|
|
145
|
+
|
|
125
146
|
The first time You are notified in writing that You have violated these terms, or done anything with the Software not covered by these terms, Your licenses can continue if You:
|
|
126
147
|
|
|
127
|
-
come into full compliance with these terms;
|
|
128
|
-
take practical steps to correct past violations; and
|
|
129
|
-
complete both within 32 days of receiving written notice.
|
|
148
|
+
1. come into full compliance with these terms;
|
|
149
|
+
2. take practical steps to correct past violations; and
|
|
150
|
+
3. complete both within 32 days of receiving written notice.
|
|
151
|
+
|
|
130
152
|
If You do not satisfy those requirements, all licenses granted to You under these terms end immediately.
|
|
131
153
|
|
|
132
154
|
For any later violation, all licenses granted to You under these terms end immediately without any cure period.
|
|
133
155
|
|
|
134
|
-
17. Termination
|
|
135
|
-
|
|
156
|
+
## 17. Termination
|
|
157
|
+
|
|
158
|
+
When Your licenses end, You must stop all use of the Software and destroy all copies of the Software in Your possession or control, except copies You are legally required to retain for archival, compliance, or legal purposes.
|
|
136
159
|
|
|
137
160
|
Sections concerning ownership, prohibited uses, trademarks, patent defense, no other rights, violations, termination, no warranty, limitation of liability, and general terms survive termination.
|
|
138
161
|
|
|
139
|
-
18. No Warranty
|
|
162
|
+
## 18. No Warranty
|
|
163
|
+
|
|
140
164
|
As far as the law allows, the Software is provided as is and as available, without any warranty or condition, express or implied.
|
|
141
165
|
|
|
142
166
|
The Licensor disclaims all warranties and conditions, including warranties of merchantability, fitness for a particular purpose, title, non-infringement, availability, accuracy, security, and quiet enjoyment.
|
|
143
167
|
|
|
144
|
-
19. Limitation of Liability
|
|
168
|
+
## 19. Limitation of Liability
|
|
169
|
+
|
|
145
170
|
As far as the law allows, the Licensor will not be liable to You for any indirect, incidental, special, consequential, exemplary, punitive, or other damages arising out of these terms or the Software.
|
|
146
171
|
|
|
147
172
|
As far as the law allows, the Licensor will not be liable for lost profits, lost revenue, lost savings, lost data, business interruption, security incidents, procurement of substitute services, or loss of goodwill.
|
|
148
173
|
|
|
149
174
|
The Licensor's total liability arising out of these terms or the Software will not exceed the amount You paid the Licensor for the Software during the 12 months before the event giving rise to liability, or USD $100 if You paid nothing.
|
|
150
175
|
|
|
151
|
-
20. Commercial Licensing
|
|
176
|
+
## 20. Commercial Licensing
|
|
177
|
+
|
|
152
178
|
Rights not granted under this license may be available under a separate Commercial License from the Licensor.
|
|
153
179
|
|
|
154
180
|
A Commercial License is required for resale, redistribution, SaaS, managed services, white-label use, OEM use, marketplace distribution, partner use, commercial hosting, commercial support offerings, or any use outside Internal Business Use.
|
|
155
181
|
|
|
156
182
|
To request commercial rights, contact:
|
|
157
183
|
|
|
158
|
-
spine-framework.com
|
|
159
|
-
webmaster@spine-framework.com
|
|
160
|
-
|
|
184
|
+
- spine-framework.com
|
|
185
|
+
- webmaster@spine-framework.com
|
|
186
|
+
|
|
187
|
+
## 21. License Updates
|
|
188
|
+
|
|
161
189
|
The Licensor may publish updated versions of this license from time to time.
|
|
162
190
|
|
|
163
191
|
Each release, package, download, or copy of the Software is governed by the license terms included with that release, package, download, or copy, unless You and the Licensor have signed a separate written agreement.
|
|
@@ -168,7 +196,8 @@ The Licensor may require acceptance of updated license terms as a condition of r
|
|
|
168
196
|
|
|
169
197
|
Nothing in this section gives You rights to continue using future versions of the Software under earlier license terms.
|
|
170
198
|
|
|
171
|
-
22. General Terms
|
|
199
|
+
## 22. General Terms
|
|
200
|
+
|
|
172
201
|
If any part of these terms is held unenforceable, the rest remains in effect as far as the law allows.
|
|
173
202
|
|
|
174
203
|
Failure by the Licensor to enforce any term is not a waiver.
|
|
@@ -177,7 +206,8 @@ Any waiver must be in writing and signed by the Licensor.
|
|
|
177
206
|
|
|
178
207
|
These terms are the entire license terms for the Software unless You and the Licensor have signed a separate written agreement.
|
|
179
208
|
|
|
180
|
-
23. Plain-English Summary
|
|
209
|
+
## 23. Plain-English Summary
|
|
210
|
+
|
|
181
211
|
This summary is not part of the legal license.
|
|
182
212
|
|
|
183
213
|
You may use Spine Framework inside your own company for your own internal business operations.
|
package/docs/README.md
CHANGED
|
@@ -1,170 +1,136 @@
|
|
|
1
|
-
# Spine
|
|
1
|
+
# Spine - Internal Dev Repo
|
|
2
2
|
|
|
3
|
-
**A multi-tenant,
|
|
3
|
+
**A multi-tenant, event-driven operational OS for modern SaaS systems.**
|
|
4
4
|
|
|
5
|
-
Spine
|
|
5
|
+
Spine is not a CRM, a helpdesk, or a workflow tool. It is the operational nervous system underneath applications — the central coordination layer that handles identity, state machines, audit trails, event emission, automation, webhooks, semantic intelligence, and theming so that vertical products don't have to rebuild these primitives.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> See [docs/MANIFESTO.md](docs/MANIFESTO.md) for the full design philosophy.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|-----------|---------|
|
|
11
|
-
| **Accounts** | Multi-tenant organizations with RLS isolation |
|
|
12
|
-
| **People** | Users with cross-account identities |
|
|
13
|
-
| **Items** | Typed records (deals, tickets, posts, custom entities) |
|
|
14
|
-
| **Types** | Runtime-defined schemas with permissions |
|
|
15
|
-
| **Apps** | Modular feature packages |
|
|
16
|
-
| **Realtime** | Live subscriptions to data changes |
|
|
17
|
-
| **RLS Security** | Row-level security with role-based access |
|
|
9
|
+
## What Spine Knows
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
| Primitive | Not Domain-Specific |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Accounts | Not "companies" or "clients" |
|
|
14
|
+
| Persons | Not "employees" or "candidates" |
|
|
15
|
+
| Workflow Items | Not "deals" or "jobs" |
|
|
16
|
+
| Events | Not "notifications" |
|
|
17
|
+
| Automation Rules | Not "zapier zaps" |
|
|
18
|
+
| Embeddings | Not "documents" |
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
# Create new project
|
|
23
|
-
npm init spine-framework@next
|
|
24
|
-
|
|
25
|
-
# Or add to existing project
|
|
26
|
-
npm install spine-framework@next
|
|
27
|
-
|
|
28
|
-
# Start development
|
|
29
|
-
npm run dev
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
|
|
34
|
-
See [INSTALL.md](INSTALL.md) for detailed setup instructions.
|
|
20
|
+
Spine provides the operational backbone. Your vertical product provides the meaning.
|
|
35
21
|
|
|
36
22
|
## Architecture
|
|
37
23
|
|
|
38
|
-
|
|
24
|
+
- **Frontend**: React 18 + Vite + TailwindCSS + shadcn/ui
|
|
25
|
+
- **API**: Netlify Functions (TypeScript)
|
|
26
|
+
- **Database**: Supabase Postgres + pgvector
|
|
27
|
+
- **Auth**: Supabase Auth (JWT) — browser handles login/session; all data access via API
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
- **Backend**: Supabase (PostgreSQL + Auth + Realtime)
|
|
42
|
-
- **Edge**: Netlify/Vercel Edge Functions
|
|
43
|
-
- **Extension**: Custom workspace pattern
|
|
29
|
+
### Core Principles
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
- **Event-driven**: Every state change produces an outbox event. Every event can trigger automations and webhooks.
|
|
32
|
+
- **Multi-tenant**: Every row scoped by `account_id`. Every request resolves person, account, and role context.
|
|
33
|
+
- **Automation-native**: Automation rules are first-class primitives with condition evaluation and action execution.
|
|
34
|
+
- **Audit-first**: Every write emits `audit_log` (before/after diff) and `activity_event` records. Nothing mutates silently.
|
|
35
|
+
- **AI-aware**: Multi-vector embeddings (pgvector) with auto-indexing on write and semantic search.
|
|
36
|
+
- **API-first**: Frontend never touches the database. All reads/writes go through Netlify Functions.
|
|
37
|
+
- **RBAC**: Two-tier — `account_role` (admin/operator/member) + `system_role` (system_admin/system_operator/support_operator).
|
|
46
38
|
|
|
47
|
-
##
|
|
39
|
+
## Modules
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
│ └── migrations/ # Custom migrations
|
|
60
|
-
├── docs/ # Documentation
|
|
61
|
-
└── package.json
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Core Principles
|
|
65
|
-
|
|
66
|
-
1. **Framework-Agnostic Core** — Base platform knows nothing of specific apps
|
|
67
|
-
2. **App-Defined Data** — Types, roles, and schemas defined by apps
|
|
68
|
-
3. **Custom Workspace** — Extensions in `custom/` without modifying core
|
|
69
|
-
4. **RLS-First Security** — All permissions enforced at database level
|
|
70
|
-
|
|
71
|
-
## Apps
|
|
41
|
+
| Module | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **Identity & Tenancy** | Accounts, persons, profiles, memberships, invites, RBAC, auto-provisioning |
|
|
44
|
+
| **Workflow Engine** | Definitions, stage builder, items, kanban, stage transitions, automation rules |
|
|
45
|
+
| **Support** | Tickets, threaded messages, knowledge base articles |
|
|
46
|
+
| **Activity & Audit** | Append-only activity feed + immutable audit log |
|
|
47
|
+
| **Integrations** | Outbox events, webhook subscriptions, HMAC-signed deliveries with retry/backoff/dead-letter |
|
|
48
|
+
| **Automation** | Event-triggered rules with conditions, actions (transition, emit, webhook, update) |
|
|
49
|
+
| **Intelligence** | Multi-vector embeddings, auto-embed on write, semantic search UI |
|
|
50
|
+
| **Theming** | DB-stored tokens, 3 presets (Clean/Bold/Muted), live Theme Editor |
|
|
72
51
|
|
|
73
|
-
|
|
52
|
+
## Setup
|
|
74
53
|
|
|
75
|
-
|
|
76
|
-
- **
|
|
77
|
-
- **Your App** — Build and install custom apps
|
|
78
|
-
|
|
79
|
-
See [APPS.md](APPS.md) for app development guide.
|
|
80
|
-
|
|
81
|
-
## Development
|
|
54
|
+
> ### TODO
|
|
55
|
+
> - [ ] Enable **Leaked password protection** in Supabase: Dashboard → **Auth → Settings → Password security**.
|
|
82
56
|
|
|
83
57
|
### Prerequisites
|
|
84
58
|
|
|
85
59
|
- Node.js 18+
|
|
86
|
-
-
|
|
87
|
-
-
|
|
60
|
+
- Netlify CLI (`npm i -g netlify-cli`)
|
|
61
|
+
- Supabase project (with pgvector enabled)
|
|
88
62
|
|
|
89
|
-
###
|
|
63
|
+
### Environment Variables
|
|
90
64
|
|
|
91
|
-
|
|
92
|
-
# Install dependencies
|
|
93
|
-
npm install
|
|
65
|
+
Copy `.env.example` to `.env` and fill in:
|
|
94
66
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
npx spine-cli db:migrate
|
|
101
|
-
|
|
102
|
-
# Start dev server
|
|
103
|
-
npm run dev
|
|
67
|
+
```
|
|
68
|
+
VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
69
|
+
VITE_SUPABASE_ANON_KEY=your-anon-key
|
|
70
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
71
|
+
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
104
72
|
```
|
|
105
73
|
|
|
106
|
-
###
|
|
74
|
+
### Database Setup
|
|
107
75
|
|
|
108
|
-
|
|
109
|
-
# From npm
|
|
110
|
-
npm install spine-framework-cortex@next
|
|
111
|
-
npx spine-cli app:install cortex
|
|
76
|
+
Apply migrations in order (001–016) via Supabase SQL editor or CLI:
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
78
|
+
```bash
|
|
79
|
+
# Each file in supabase/migrations/ should be applied sequentially
|
|
115
80
|
```
|
|
116
81
|
|
|
117
|
-
###
|
|
82
|
+
### Local Development
|
|
118
83
|
|
|
119
84
|
```bash
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Define types in custom/apps/my-app/seed/types.json
|
|
124
|
-
# Create pages in custom/apps/my-app/pages/
|
|
125
|
-
# Wire up routes in custom/apps/my-app/index.tsx
|
|
126
|
-
|
|
127
|
-
# Install and test
|
|
128
|
-
npx spine-cli app:install ./custom/apps/my-app
|
|
129
|
-
npm run dev
|
|
85
|
+
npm install
|
|
86
|
+
netlify dev
|
|
130
87
|
```
|
|
131
88
|
|
|
132
|
-
|
|
89
|
+
This starts:
|
|
90
|
+
- Vite dev server on port 5173
|
|
91
|
+
- Netlify Functions proxy on port 8888
|
|
92
|
+
- App at `http://localhost:8888`
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
|----------|-------------|
|
|
136
|
-
| [INSTALL.md](INSTALL.md) | Installation guide |
|
|
137
|
-
| [ARCHITECTURE.md](ARCHITECTURE.md) | System architecture |
|
|
138
|
-
| [APPS.md](APPS.md) | App development |
|
|
139
|
-
| [spine-core--api-reference.md](spine-core--api-reference.md) | API reference |
|
|
140
|
-
| [dev-guides/](dev-guides/) | Step-by-step tutorials |
|
|
94
|
+
New users are auto-provisioned on first login — no manual database setup required.
|
|
141
95
|
|
|
142
|
-
|
|
96
|
+
### Build
|
|
143
97
|
|
|
144
98
|
```bash
|
|
145
|
-
|
|
146
|
-
npx spine-cli db:migrate # Apply migrations
|
|
147
|
-
npx spine-cli db:seed # Apply seed data
|
|
148
|
-
npx spine-cli app:install <name> # Install an app
|
|
149
|
-
npx spine-cli app:create <name> # Create new app scaffold
|
|
99
|
+
npm run build
|
|
150
100
|
```
|
|
151
101
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
| Package | Description |
|
|
155
|
-
|---------|-------------|
|
|
156
|
-
| `spine-framework` | Core framework |
|
|
157
|
-
| `spine-framework-portal` | Customer portal app |
|
|
158
|
-
| `spine-framework-cortex` | CRM & support app |
|
|
159
|
-
|
|
160
|
-
All packages use the `@next` tag for pre-release versions.
|
|
161
|
-
|
|
162
|
-
## License
|
|
163
|
-
|
|
164
|
-
MIT License — see LICENSE file for details.
|
|
102
|
+
## Project Structure
|
|
165
103
|
|
|
166
|
-
|
|
104
|
+
```
|
|
105
|
+
├── core/ # Canonical Spine runtime (DO NOT EDIT)
|
|
106
|
+
│ ├── functions/ # 59 endpoint handlers
|
|
107
|
+
│ │ ├── _shared/ # Shared utilities (middleware, db, audit, etc.)
|
|
108
|
+
│ │ └── *.ts # Core API endpoints
|
|
109
|
+
│ └── src/ # Core React frontend
|
|
110
|
+
│ ├── components/ # UI components (ui/, layout/, shared/, etc.)
|
|
111
|
+
│ ├── pages/ # Route pages (admin, portal, public)
|
|
112
|
+
│ ├── lib/ # Utilities (api, auth, config, theme, etc.)
|
|
113
|
+
│ └── hooks/ # React hooks (useAuth, useCustomFields, etc.)
|
|
114
|
+
├── custom/ # Your extensions (MODIFY FREELY)
|
|
115
|
+
│ ├── functions/ # Custom serverless functions (override/extend core)
|
|
116
|
+
│ └── src/ # Custom frontend code
|
|
117
|
+
│ └── manifest/routes.ts # Custom route + nav registrations
|
|
118
|
+
├── src/ # Assembled frontend output (auto-generated, .gitignored)
|
|
119
|
+
├── netlify/functions/ # Assembled API output (auto-generated, .gitignored)
|
|
120
|
+
├── supabase/
|
|
121
|
+
│ └── migrations/ # 33 SQL migrations
|
|
122
|
+
├── scripts/
|
|
123
|
+
│ ├── assemble-functions.sh # Merge core/functions + custom/functions
|
|
124
|
+
│ ├── assemble-frontend.sh # Merge core/src + custom/src → src/
|
|
125
|
+
│ ├── build-manifest.sh # Generate integrity hashes
|
|
126
|
+
│ └── verify-integrity.sh # Verify core integrity
|
|
127
|
+
├── docs/
|
|
128
|
+
│ ├── MANIFESTO.md # Design philosophy
|
|
129
|
+
│ ├── FRONTEND.md # Frontend architecture
|
|
130
|
+
│ ├── EXTENDING.md # Extension guide
|
|
131
|
+
│ └── API.md # API reference
|
|
132
|
+
├── netlify.toml
|
|
133
|
+
└── package.json
|
|
134
|
+
```
|
|
167
135
|
|
|
168
|
-
|
|
169
|
-
- Issues: https://github.com/spine-framework/spine/issues
|
|
170
|
-
- Discussions: https://github.com/spine-framework/spine/discussions
|
|
136
|
+
# Testing GIT flow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.106",
|
|
4
4
|
"description": "Multi-tenant, modular application platform for modern SaaS systems",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
".framework/migrations/019_triggers_unique_constraint.sql",
|
|
29
29
|
".framework/migrations/020_get_account_apps_role_filter.sql",
|
|
30
30
|
".framework/migrations/021_app_domains.sql",
|
|
31
|
+
".framework/migrations/022_route_prefix_domain_unique.sql",
|
|
32
|
+
".framework/migrations/023_match_embeddings_rpc.sql",
|
|
33
|
+
".framework/migrations/024_messages_account_id.sql",
|
|
31
34
|
".framework/public/",
|
|
32
35
|
".framework/scripts/",
|
|
33
36
|
".framework/seeds/",
|
|
@@ -18,8 +18,9 @@ echo ""
|
|
|
18
18
|
VIOLATIONS=0
|
|
19
19
|
|
|
20
20
|
# Check 1: Core importing from custom/ directory
|
|
21
|
+
# Only flag actual import/require statements — comments and docs are fine
|
|
21
22
|
echo "1. Checking for imports from custom/ in core..."
|
|
22
|
-
if grep -
|
|
23
|
+
if grep -rE "^\s*(import|export).*['\"].*custom/|require\(['\"].*custom/" .framework/ --include="*.ts" --include="*.tsx" --exclude-dir=tests 2>/dev/null; then
|
|
23
24
|
echo " ❌ FAIL: Core files import from custom/"
|
|
24
25
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
25
26
|
else
|
|
@@ -48,8 +49,11 @@ fi
|
|
|
48
49
|
echo ""
|
|
49
50
|
|
|
50
51
|
# Check 3: App-specific references in core
|
|
52
|
+
# Only flag actual code (imports, JSX, logic) — comments, JSDoc, and string literals in examples are fine
|
|
51
53
|
echo "3. Checking for app-specific references in core..."
|
|
52
|
-
APP_REFS=$(grep -
|
|
54
|
+
APP_REFS=$(grep -rE "^\s*(import|export|from|<[A-Z])" .framework/src/ --include="*.ts" --include="*.tsx" 2>/dev/null \
|
|
55
|
+
| grep -E "cortex|funnel|support-triage|kb-chunker|portal-signals" \
|
|
56
|
+
| grep -v "manifest\.json\|webhook-registry" || true)
|
|
53
57
|
if [ -n "$APP_REFS" ]; then
|
|
54
58
|
echo " ❌ FAIL: App-specific references found in core:"
|
|
55
59
|
echo "$APP_REFS"
|