spine-framework 0.2.9 → 0.3.1

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,70 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * @module config/signals
4
+ * @audience installer
5
+ * @layer cli
6
+ * @stability stable
7
+ *
8
+ * Signal endpoint configuration for framework install tracking and engagement.
9
+ *
10
+ * The public framework sends anonymous signals to a configurable endpoint.
11
+ * Private Cortex instances receive and process these signals.
12
+ */
13
+
14
+ export interface SignalConfig {
15
+ endpoint: string
16
+ timeout: number
17
+ retryAttempts: number
18
+ batchSize: number
19
+ }
20
+
21
+ export const signalConfig: SignalConfig = {
22
+ endpoint: process.env.SPINE_SIGNAL_ENDPOINT || 'https://cortex.spine-framework.com',
23
+ timeout: 5000,
24
+ retryAttempts: 3,
25
+ batchSize: 10,
26
+ }
27
+
28
+ // Engagement signal types with scores
29
+ export const engagementSignals = {
30
+ framework_installed: { score: 1, description: 'Framework npm install completion' },
31
+ framework_initialized: { score: 3, description: 'First successful startup' },
32
+ framework_connected_db: { score: 5, description: 'Database connection success' },
33
+ app_install: { score: 4, description: 'App installation' },
34
+ app_initialized: { score: 4, description: 'App first load' },
35
+ app_first_action: { score: 6, description: 'First feature usage' },
36
+ account_created: { score: 8, description: 'Anonymous → identified migration' },
37
+ first_deploy: { score: 10, description: 'Production deployment' },
38
+ team_collaboration: { score: 12, description: 'Multi-user usage' },
39
+ } as const
40
+
41
+ export type EngagementSignalType = keyof typeof engagementSignals
42
+
43
+ // Claim trigger thresholds
44
+ export const claimTriggers = {
45
+ immediate: 0, // User explicitly runs `spine-framework claim`
46
+ gentle_nudge: 3, // After first successful startup
47
+ value_realized: 5, // After database connection
48
+ ecosystem_engaged: 8, // After app installation
49
+ power_user: 10, // After regular usage patterns
50
+ } as const
51
+
52
+ export interface EngagementEntry {
53
+ type: EngagementSignalType
54
+ timestamp: string
55
+ score: number
56
+ }
57
+
58
+ export interface EngagementData {
59
+ framework_install_serial: string
60
+ engagement_score: number
61
+ signals: EngagementEntry[]
62
+ claim_status: 'anonymous' | 'claimed' | 'pending'
63
+ last_claim_prompt?: string
64
+ account_id?: string
65
+ }
66
+
67
+ export interface ClaimPrompt {
68
+ timing: keyof typeof claimTriggers
69
+ message: string
70
+ }
@@ -862,6 +862,15 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_roles_slug_no_app
862
862
  CREATE UNIQUE INDEX IF NOT EXISTS idx_roles_slug_with_app
863
863
  ON public.roles (app_id, slug)
864
864
  WHERE app_id IS NOT NULL;
865
+ -- Non-partial unique constraint required for PostgREST ON CONFLICT upsert on (app_id, slug)
866
+ DO $$ BEGIN
867
+ IF NOT EXISTS (
868
+ SELECT 1 FROM pg_constraint
869
+ WHERE conname = 'roles_app_id_slug_key' AND conrelid = 'public.roles'::regclass
870
+ ) THEN
871
+ ALTER TABLE public.roles ADD CONSTRAINT roles_app_id_slug_key UNIQUE (app_id, slug);
872
+ END IF;
873
+ END $$;
865
874
 
866
875
  CREATE INDEX IF NOT EXISTS idx_se_account ON public.schedule_executions (account_id);
867
876
  CREATE INDEX IF NOT EXISTS idx_se_schedule ON public.schedule_executions (schedule_id);
@@ -1279,6 +1288,22 @@ BEGIN
1279
1288
  END;
1280
1289
  $$;
1281
1290
 
1291
+ -- ---------------------------------------------------------------------------
1292
+ -- App Installations
1293
+ -- Tracks which apps have been installed via `spine-framework install-app`
1294
+ -- ---------------------------------------------------------------------------
1295
+ CREATE TABLE IF NOT EXISTS public.app_installations (
1296
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1297
+ app_slug text NOT NULL,
1298
+ account_id uuid,
1299
+ is_enabled boolean DEFAULT true,
1300
+ installed_version text,
1301
+ package_name text,
1302
+ created_at timestamptz DEFAULT now(),
1303
+ updated_at timestamptz DEFAULT now(),
1304
+ UNIQUE (app_slug, account_id)
1305
+ );
1306
+
1282
1307
  -- ---------------------------------------------------------------------------
1283
1308
  -- Role Grants
1284
1309
  -- Raw SQL migrations bypass Supabase's automatic grant setup. These grants
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "Spine — enterprise application framework built on Supabase + Netlify + React",
6
6
  "type": "module",
@@ -51,6 +51,7 @@
51
51
  "bin/",
52
52
  "dist/",
53
53
  ".framework/cli/",
54
+ ".framework/config/",
54
55
  ".framework/functions/",
55
56
  ".framework/migrations/",
56
57
  ".framework/docs/",