@intelligo-dev/core 1.0.0-beta.1 → 1.0.0-beta.14
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/NOTICE +6 -0
- package/README.md +102 -0
- package/dist/attachments/errors.d.ts +7 -0
- package/dist/attachments/errors.d.ts.map +1 -0
- package/dist/attachments/errors.js +11 -0
- package/dist/attachments/errors.js.map +1 -0
- package/dist/attachments/index.d.ts +8 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +7 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/attachments/service.d.ts +45 -0
- package/dist/attachments/service.d.ts.map +1 -0
- package/dist/attachments/service.js +100 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/attachments/types.d.ts +6 -0
- package/dist/attachments/types.d.ts.map +1 -0
- package/dist/attachments/types.js +2 -0
- package/dist/attachments/types.js.map +1 -0
- package/dist/conversations/errors.d.ts +2 -8
- package/dist/conversations/errors.d.ts.map +1 -1
- package/dist/conversations/errors.js +2 -8
- package/dist/conversations/errors.js.map +1 -1
- package/dist/conversations/index.d.ts +4 -12
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/index.js +2 -11
- package/dist/conversations/index.js.map +1 -1
- package/dist/conversations/service.d.ts +49 -61
- package/dist/conversations/service.d.ts.map +1 -1
- package/dist/conversations/service.js +110 -83
- package/dist/conversations/service.js.map +1 -1
- package/dist/conversations/types.d.ts +1 -4
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/conversations/types.js +0 -3
- package/dist/conversations/types.js.map +1 -1
- package/dist/db/client.d.ts +21 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +43 -18
- package/dist/db/client.js.map +1 -1
- package/dist/db/index.d.ts +3 -3
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +3 -7
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/agents.d.ts +14 -51
- package/dist/db/schema/agents.d.ts.map +1 -1
- package/dist/db/schema/agents.js +9 -21
- package/dist/db/schema/agents.js.map +1 -1
- package/dist/db/schema/ai.d.ts +86 -1556
- package/dist/db/schema/ai.d.ts.map +1 -1
- package/dist/db/schema/ai.js +32 -236
- package/dist/db/schema/ai.js.map +1 -1
- package/dist/db/schema/auth.d.ts +5 -34
- package/dist/db/schema/auth.d.ts.map +1 -1
- package/dist/db/schema/auth.js +25 -56
- package/dist/db/schema/auth.js.map +1 -1
- package/dist/db/schema/billing.d.ts +339 -105
- package/dist/db/schema/billing.d.ts.map +1 -1
- package/dist/db/schema/billing.js +99 -58
- package/dist/db/schema/billing.js.map +1 -1
- package/dist/db/schema/feature-flags.d.ts +3 -12
- package/dist/db/schema/feature-flags.d.ts.map +1 -1
- package/dist/db/schema/feature-flags.js +3 -12
- package/dist/db/schema/feature-flags.js.map +1 -1
- package/dist/db/schema/identity.d.ts +12 -274
- package/dist/db/schema/identity.d.ts.map +1 -1
- package/dist/db/schema/identity.js +19 -104
- package/dist/db/schema/identity.js.map +1 -1
- package/dist/db/schema/index.d.ts +8 -15
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/index.js +12 -42
- package/dist/db/schema/index.js.map +1 -1
- package/dist/db/schema/notifications.d.ts +3 -17
- package/dist/db/schema/notifications.d.ts.map +1 -1
- package/dist/db/schema/notifications.js +3 -17
- package/dist/db/schema/notifications.js.map +1 -1
- package/dist/db/schema/usage.d.ts +94 -77
- package/dist/db/schema/usage.d.ts.map +1 -1
- package/dist/db/schema/usage.js +59 -89
- package/dist/db/schema/usage.js.map +1 -1
- package/dist/db/validate.d.ts +2 -11
- package/dist/db/validate.d.ts.map +1 -1
- package/dist/db/validate.js +6 -17
- package/dist/db/validate.js.map +1 -1
- package/dist/db/workspace-queries.d.ts +5 -29
- package/dist/db/workspace-queries.d.ts.map +1 -1
- package/dist/db/workspace-queries.js +5 -29
- package/dist/db/workspace-queries.js.map +1 -1
- package/dist/documents/classifier.d.ts +5 -12
- package/dist/documents/classifier.d.ts.map +1 -1
- package/dist/documents/classifier.js +16 -16
- package/dist/documents/classifier.js.map +1 -1
- package/dist/documents/errors.d.ts +2 -10
- package/dist/documents/errors.d.ts.map +1 -1
- package/dist/documents/errors.js +2 -10
- package/dist/documents/errors.js.map +1 -1
- package/dist/documents/index.d.ts +4 -13
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +3 -12
- package/dist/documents/index.js.map +1 -1
- package/dist/documents/service.d.ts +19 -26
- package/dist/documents/service.d.ts.map +1 -1
- package/dist/documents/service.js +44 -38
- package/dist/documents/service.js.map +1 -1
- package/dist/documents/types.d.ts +6 -10
- package/dist/documents/types.d.ts.map +1 -1
- package/dist/documents/types.js +0 -3
- package/dist/documents/types.js.map +1 -1
- package/dist/email/brand.d.ts +17 -0
- package/dist/email/brand.d.ts.map +1 -0
- package/dist/email/brand.js +9 -0
- package/dist/email/brand.js.map +1 -0
- package/dist/email/index.d.ts +4 -4
- package/dist/email/index.js +4 -4
- package/dist/email/provider.d.ts +1 -7
- package/dist/email/provider.d.ts.map +1 -1
- package/dist/email/provider.js +10 -19
- package/dist/email/provider.js.map +1 -1
- package/dist/email/send.d.ts +12 -12
- package/dist/email/send.d.ts.map +1 -1
- package/dist/email/send.js +5 -19
- package/dist/email/send.js.map +1 -1
- package/dist/email/senders.d.ts +17 -30
- package/dist/email/senders.d.ts.map +1 -1
- package/dist/email/senders.js +33 -47
- package/dist/email/senders.js.map +1 -1
- package/dist/email/templates/base-layout.d.ts +1 -1
- package/dist/email/templates/base-layout.d.ts.map +1 -1
- package/dist/email/templates/base-layout.js +4 -6
- package/dist/email/templates/base-layout.js.map +1 -1
- package/dist/email/templates/index.d.ts +10 -10
- package/dist/email/templates/index.d.ts.map +1 -1
- package/dist/email/templates/index.js +10 -16
- package/dist/email/templates/index.js.map +1 -1
- package/dist/email/templates/password-reset.d.ts +2 -2
- package/dist/email/templates/password-reset.d.ts.map +1 -1
- package/dist/email/templates/password-reset.js +10 -22
- package/dist/email/templates/password-reset.js.map +1 -1
- package/dist/email/templates/payment-failed.d.ts +2 -2
- package/dist/email/templates/payment-failed.d.ts.map +1 -1
- package/dist/email/templates/payment-failed.js +11 -24
- package/dist/email/templates/payment-failed.js.map +1 -1
- package/dist/email/templates/quota-warning.d.ts +4 -4
- package/dist/email/templates/quota-warning.d.ts.map +1 -1
- package/dist/email/templates/quota-warning.js +19 -39
- package/dist/email/templates/quota-warning.js.map +1 -1
- package/dist/email/templates/subscription-confirmed.d.ts +2 -2
- package/dist/email/templates/subscription-confirmed.d.ts.map +1 -1
- package/dist/email/templates/subscription-confirmed.js +13 -28
- package/dist/email/templates/subscription-confirmed.js.map +1 -1
- package/dist/email/templates/trial-expiry.d.ts +2 -2
- package/dist/email/templates/trial-expiry.d.ts.map +1 -1
- package/dist/email/templates/trial-expiry.js +12 -36
- package/dist/email/templates/trial-expiry.js.map +1 -1
- package/dist/email/templates/trial-warning.d.ts +2 -2
- package/dist/email/templates/trial-warning.d.ts.map +1 -1
- package/dist/email/templates/trial-warning.js +15 -32
- package/dist/email/templates/trial-warning.js.map +1 -1
- package/dist/email/templates/verify-email.d.ts +2 -2
- package/dist/email/templates/verify-email.d.ts.map +1 -1
- package/dist/email/templates/verify-email.js +10 -22
- package/dist/email/templates/verify-email.js.map +1 -1
- package/dist/email/templates/welcome.d.ts +2 -2
- package/dist/email/templates/welcome.d.ts.map +1 -1
- package/dist/email/templates/welcome.js +13 -20
- package/dist/email/templates/welcome.js.map +1 -1
- package/dist/email/templates/workspace-invitation.d.ts +2 -2
- package/dist/email/templates/workspace-invitation.d.ts.map +1 -1
- package/dist/email/templates/workspace-invitation.js +12 -26
- package/dist/email/templates/workspace-invitation.js.map +1 -1
- package/dist/env.d.ts +5 -10
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +41 -31
- package/dist/env.js.map +1 -1
- package/dist/identity/audit.d.ts +11 -24
- package/dist/identity/audit.d.ts.map +1 -1
- package/dist/identity/audit.js +13 -27
- package/dist/identity/audit.js.map +1 -1
- package/dist/identity/errors.d.ts +4 -12
- package/dist/identity/errors.d.ts.map +1 -1
- package/dist/identity/errors.js +2 -8
- package/dist/identity/errors.js.map +1 -1
- package/dist/identity/index.d.ts +6 -13
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +5 -12
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/service.d.ts +29 -47
- package/dist/identity/service.d.ts.map +1 -1
- package/dist/identity/service.js +107 -79
- package/dist/identity/service.js.map +1 -1
- package/dist/identity/types.d.ts +28 -4
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/identity/types.js +0 -3
- package/dist/identity/types.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -10
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +7 -18
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +45 -49
- package/dist/logger.js.map +1 -1
- package/dist/money.d.ts +95 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +208 -0
- package/dist/money.js.map +1 -0
- package/dist/notifications/index.d.ts +9 -43
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +9 -49
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/triggers.d.ts +9 -32
- package/dist/notifications/triggers.d.ts.map +1 -1
- package/dist/notifications/triggers.js +21 -58
- package/dist/notifications/triggers.js.map +1 -1
- package/dist/notifications/types.d.ts +6 -8
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js +0 -9
- package/dist/notifications/types.js.map +1 -1
- package/dist/prompt.d.ts +38 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +118 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +60 -0
- package/dist/registry.js.map +1 -0
- package/dist/request-context.d.ts +49 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +100 -0
- package/dist/request-context.js.map +1 -0
- package/dist/storage/index.d.ts +65 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +109 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +74 -24
- package/src/attachments/errors.ts +18 -0
- package/src/attachments/index.ts +22 -0
- package/src/attachments/service.ts +178 -0
- package/src/attachments/types.ts +6 -0
- package/src/conversations/errors.ts +45 -0
- package/src/conversations/index.ts +39 -0
- package/src/conversations/service.ts +594 -0
- package/src/conversations/types.ts +15 -0
- package/src/db/client.ts +91 -0
- package/src/db/index.ts +3 -0
- package/src/db/migrations/0000_baseline.sql +652 -0
- package/src/db/migrations/0001_reconcile_chain_built_databases.sql +53 -0
- package/src/db/migrations/0002_webhook_claim_lease.sql +1 -0
- package/src/db/migrations/0003_local_payments.sql +18 -0
- package/src/db/migrations/README.md +103 -36
- package/src/db/migrations/legacy-chain.json +206 -0
- package/src/db/migrations/meta/0000_snapshot.json +4182 -157
- package/src/db/migrations/meta/0002_snapshot.json +2478 -368
- package/src/db/migrations/meta/0003_snapshot.json +2567 -554
- package/src/db/migrations/meta/_journal.json +8 -274
- package/src/db/schema/agents.ts +57 -0
- package/src/db/schema/ai.ts +174 -0
- package/src/db/schema/auth.ts +183 -0
- package/src/db/schema/billing.ts +245 -0
- package/src/db/schema/feature-flags.ts +20 -0
- package/src/db/schema/identity.ts +250 -0
- package/src/db/schema/index.ts +12 -0
- package/src/db/schema/notifications.ts +31 -0
- package/src/db/schema/usage.ts +269 -0
- package/src/db/validate.ts +63 -0
- package/src/db/workspace-queries.ts +64 -0
- package/src/documents/classifier.ts +61 -0
- package/src/documents/errors.ts +44 -0
- package/src/documents/index.ts +24 -0
- package/src/documents/service.ts +256 -0
- package/src/documents/types.ts +24 -0
- package/src/email/brand.ts +24 -0
- package/src/email/index.ts +19 -0
- package/src/email/provider.ts +278 -0
- package/src/email/send.ts +169 -0
- package/src/email/senders.ts +292 -0
- package/src/email/templates/base-layout.tsx +145 -0
- package/src/email/templates/index.ts +28 -0
- package/src/email/templates/password-reset.tsx +72 -0
- package/src/email/templates/payment-failed.tsx +77 -0
- package/src/email/templates/quota-warning.tsx +118 -0
- package/src/email/templates/subscription-confirmed.tsx +91 -0
- package/src/email/templates/trial-expiry.tsx +124 -0
- package/src/email/templates/trial-warning.tsx +104 -0
- package/src/email/templates/verify-email.tsx +72 -0
- package/src/email/templates/welcome.tsx +64 -0
- package/src/email/templates/workspace-invitation.tsx +105 -0
- package/src/env.ts +180 -0
- package/src/identity/audit.ts +65 -0
- package/src/identity/errors.ts +44 -0
- package/src/identity/index.ts +29 -0
- package/src/identity/service.ts +303 -0
- package/src/identity/types.ts +58 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +198 -0
- package/src/money.ts +270 -0
- package/src/notifications/index.ts +112 -0
- package/src/notifications/triggers.ts +158 -0
- package/src/notifications/types.ts +26 -0
- package/src/prompt.ts +142 -0
- package/src/registry.ts +75 -0
- package/src/request-context.ts +137 -0
- package/src/storage/index.ts +153 -0
- package/dist/db/schema/rag.d.ts +0 -171
- package/dist/db/schema/rag.d.ts.map +0 -1
- package/dist/db/schema/rag.js +0 -42
- package/dist/db/schema/rag.js.map +0 -1
- package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
- package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
- package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
- package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
- package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
- package/src/db/migrations/0005_careful_switch.sql +0 -8
- package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
- package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
- package/src/db/migrations/0008_powerful_scream.sql +0 -39
- package/src/db/migrations/0009_sad_ink.sql +0 -1
- package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
- package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
- package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
- package/src/db/migrations/0012_user_profiles.sql +0 -30
- package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
- package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
- package/src/db/migrations/0015_new_v1_tables.sql +0 -149
- package/src/db/migrations/0016_document_types.sql +0 -30
- package/src/db/migrations/0017_document_metadata.sql +0 -2
- package/src/db/migrations/0018_identity_graph.sql +0 -200
- package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
- package/src/db/migrations/0020_pending_extractions.sql +0 -35
- package/src/db/migrations/0021_rag_documents.sql +0 -39
- package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
- package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
- package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
- package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
- package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
- package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
- package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
- package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
- package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
- package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
- package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
- package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
- package/src/db/migrations/0034_rate_limit_count.sql +0 -9
- package/src/db/migrations/0035_credit_reservations.sql +0 -24
- package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
- package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
- package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
- package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
- package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
- package/src/db/migrations/meta/0001_snapshot.json +0 -631
- package/src/db/migrations/meta/0004_snapshot.json +0 -2500
- package/src/db/migrations/meta/0005_snapshot.json +0 -2548
- package/src/db/migrations/meta/0006_snapshot.json +0 -2650
- package/src/db/migrations/meta/0007_snapshot.json +0 -2657
- package/src/db/migrations/meta/0008_snapshot.json +0 -2719
- package/src/db/migrations/meta/0009_snapshot.json +0 -2726
- package/src/db/migrations/meta/0010_snapshot.json +0 -2739
- package/src/db/migrations/meta/0011_snapshot.json +0 -3886
package/src/env.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup validation of environment variables: a missing required variable
|
|
3
|
+
* throws immediately instead of when its feature is first used. Some are
|
|
4
|
+
* optional in development (STRIPE_*, RESEND_API_KEY).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type EnvVar = {
|
|
8
|
+
name: string;
|
|
9
|
+
required: boolean;
|
|
10
|
+
description: string;
|
|
11
|
+
/** Optional minimum length — used for secret strength checks */
|
|
12
|
+
minLength?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Only variables the framework itself reads. A model provider's key is
|
|
16
|
+
// absent on purpose: which provider a deployment calls is the product's
|
|
17
|
+
// choice, and its SDK reports a missing key on the first request.
|
|
18
|
+
const ENV_VARS: EnvVar[] = [
|
|
19
|
+
{
|
|
20
|
+
name: "DATABASE_URL",
|
|
21
|
+
required: true,
|
|
22
|
+
description: "PostgreSQL connection string",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "BETTER_AUTH_SECRET",
|
|
26
|
+
required: true,
|
|
27
|
+
description:
|
|
28
|
+
"Better-Auth session encryption key (AUTH_SECRET is accepted as a legacy alias)",
|
|
29
|
+
minLength: 32,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "NEXT_PUBLIC_APP_URL",
|
|
33
|
+
required: true,
|
|
34
|
+
description: "Application base URL for auth and redirects",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "STRIPE_SECRET_KEY",
|
|
38
|
+
required: false,
|
|
39
|
+
description: "Stripe API key (billing features)",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "STRIPE_WEBHOOK_SECRET",
|
|
43
|
+
required: false,
|
|
44
|
+
description: "Stripe webhook signature secret",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "RESEND_API_KEY",
|
|
48
|
+
required: false,
|
|
49
|
+
description: "Resend API key (email features)",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "LOOPS_API_KEY",
|
|
53
|
+
required: false,
|
|
54
|
+
description:
|
|
55
|
+
"Loops API key (email features; templates mapped via LOOPS_TRANSACTIONAL_ID_*)",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "EMAIL_PROVIDER",
|
|
59
|
+
required: false,
|
|
60
|
+
description: "Explicit email provider selection (resend | loops | console)",
|
|
61
|
+
},
|
|
62
|
+
// Cron endpoints gate on a Bearer header equal to CRON_SECRET. Optional
|
|
63
|
+
// in development; production should set at least 32 characters.
|
|
64
|
+
{
|
|
65
|
+
name: "CRON_SECRET",
|
|
66
|
+
required: false,
|
|
67
|
+
description: "Shared secret for /api/cron/* Bearer auth",
|
|
68
|
+
minLength: 32,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export function validateEnv(): {
|
|
73
|
+
valid: boolean;
|
|
74
|
+
errors: string[];
|
|
75
|
+
warnings: string[];
|
|
76
|
+
} {
|
|
77
|
+
const errors: string[] = [];
|
|
78
|
+
const warnings: string[] = [];
|
|
79
|
+
|
|
80
|
+
for (const envVar of ENV_VARS) {
|
|
81
|
+
let value = process.env[envVar.name];
|
|
82
|
+
// Better-Auth reads BETTER_AUTH_SECRET then AUTH_SECRET; the CLI, the
|
|
83
|
+
// scaffold and this validator use the former.
|
|
84
|
+
if (
|
|
85
|
+
envVar.name === "BETTER_AUTH_SECRET" &&
|
|
86
|
+
!value &&
|
|
87
|
+
process.env.AUTH_SECRET
|
|
88
|
+
) {
|
|
89
|
+
value = process.env.AUTH_SECRET;
|
|
90
|
+
warnings.push(
|
|
91
|
+
"AUTH_SECRET is set but BETTER_AUTH_SECRET is not — rename it; AUTH_SECRET is a legacy alias"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (!value || value.trim() === "") {
|
|
95
|
+
if (envVar.required) {
|
|
96
|
+
errors.push(
|
|
97
|
+
`Missing required env var: ${envVar.name} — ${envVar.description}`
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
warnings.push(
|
|
101
|
+
`Missing optional env var: ${envVar.name} — ${envVar.description}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (envVar.minLength && value.length < envVar.minLength) {
|
|
107
|
+
const msg = `${envVar.name} is shorter than ${envVar.minLength} chars — too weak for ${envVar.description}`;
|
|
108
|
+
if (envVar.required) {
|
|
109
|
+
errors.push(msg);
|
|
110
|
+
} else {
|
|
111
|
+
warnings.push(msg);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Resend rejects a send without a sender on a verified domain, and the
|
|
117
|
+
// framework has no domain of its own to fall back on. Loops takes the
|
|
118
|
+
// sender from each template, so only Resend is checked.
|
|
119
|
+
const emailProvider = process.env.EMAIL_PROVIDER?.toLowerCase();
|
|
120
|
+
const sendsThroughResend =
|
|
121
|
+
Boolean(process.env.RESEND_API_KEY?.trim()) &&
|
|
122
|
+
emailProvider !== "loops" &&
|
|
123
|
+
emailProvider !== "console";
|
|
124
|
+
if (sendsThroughResend && !process.env.EMAIL_FROM?.trim()) {
|
|
125
|
+
warnings.push(
|
|
126
|
+
'RESEND_API_KEY is set but EMAIL_FROM is not — every email fails until EMAIL_FROM names a sender on a domain verified with Resend, e.g. "Acme <noreply@acme.com>"'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Production safety checks
|
|
131
|
+
if (process.env.NODE_ENV === "production") {
|
|
132
|
+
const appUrl = process.env.NEXT_PUBLIC_APP_URL?.trim();
|
|
133
|
+
if (appUrl && !appUrl.startsWith("https://")) {
|
|
134
|
+
warnings.push(
|
|
135
|
+
`NEXT_PUBLIC_APP_URL is ${appUrl}, not an https URL — auth callbacks, email links and secure cookies are built from it`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (!process.env.RESEND_API_KEY && !process.env.LOOPS_API_KEY) {
|
|
139
|
+
warnings.push(
|
|
140
|
+
"No email provider (RESEND_API_KEY or LOOPS_API_KEY) — sign-ups are not email-verified and emails only reach the server console"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (process.env.STRIPE_SECRET_KEY?.startsWith("sk_test_")) {
|
|
144
|
+
warnings.push(
|
|
145
|
+
"STRIPE_SECRET_KEY is a test key in production environment"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!process.env.CRON_SECRET || process.env.CRON_SECRET.length < 32) {
|
|
149
|
+
// A warning, not an error: the framework ships no cron route of its
|
|
150
|
+
// own, and the maintenance route refuses to serve without it.
|
|
151
|
+
warnings.push(
|
|
152
|
+
"CRON_SECRET unset or shorter than 32 chars — required by any /api/cron/* route you mount"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Call during app initialization. Throws on missing required variables;
|
|
162
|
+
* logs warnings for optional ones.
|
|
163
|
+
*/
|
|
164
|
+
export function assertEnv(): void {
|
|
165
|
+
const { valid, errors, warnings } = validateEnv();
|
|
166
|
+
|
|
167
|
+
for (const warning of warnings) {
|
|
168
|
+
console.warn(`[Env] ${warning}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!valid) {
|
|
172
|
+
console.error("[Env] Missing required environment variables:");
|
|
173
|
+
for (const error of errors) {
|
|
174
|
+
console.error(` - ${error}`);
|
|
175
|
+
}
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Missing ${errors.length} required environment variable(s). See logs above.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes `user_memory_audit` rows. Core may not import
|
|
3
|
+
* `@intelligo-dev/audit` (only audit -> core is allowed), so the write lives
|
|
4
|
+
* here and audit exports only the event contract. Internal to identity; not
|
|
5
|
+
* re-exported.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { db } from "../db";
|
|
9
|
+
import { userMemoryAudit } from "../db/schema";
|
|
10
|
+
import type {
|
|
11
|
+
AuditTargetKind,
|
|
12
|
+
AuditAction,
|
|
13
|
+
AuditActorKind,
|
|
14
|
+
UserMemoryAuditRow,
|
|
15
|
+
} from "../db/schema";
|
|
16
|
+
import { IdentityServiceError } from "./errors";
|
|
17
|
+
|
|
18
|
+
export type RecordMemoryAuditInput = {
|
|
19
|
+
userId: string;
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
targetKind: AuditTargetKind;
|
|
22
|
+
targetId: string;
|
|
23
|
+
action: AuditAction;
|
|
24
|
+
actorKind: AuditActorKind;
|
|
25
|
+
actorId?: string;
|
|
26
|
+
beforeValue?: unknown;
|
|
27
|
+
afterValue?: unknown;
|
|
28
|
+
reason?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `writer` is the transaction of the mutation being audited, so the
|
|
33
|
+
* change and its record commit or fail together.
|
|
34
|
+
*/
|
|
35
|
+
export async function recordMemoryAudit(
|
|
36
|
+
input: RecordMemoryAuditInput,
|
|
37
|
+
writer: Pick<typeof db, "insert"> = db
|
|
38
|
+
): Promise<UserMemoryAuditRow> {
|
|
39
|
+
const [row] = await writer
|
|
40
|
+
.insert(userMemoryAudit)
|
|
41
|
+
.values({
|
|
42
|
+
id: crypto.randomUUID(),
|
|
43
|
+
userId: input.userId,
|
|
44
|
+
workspaceId: input.workspaceId,
|
|
45
|
+
targetKind: input.targetKind,
|
|
46
|
+
targetId: input.targetId,
|
|
47
|
+
action: input.action,
|
|
48
|
+
actorKind: input.actorKind,
|
|
49
|
+
actorId: input.actorId,
|
|
50
|
+
beforeValue: input.beforeValue,
|
|
51
|
+
afterValue: input.afterValue,
|
|
52
|
+
reason: input.reason,
|
|
53
|
+
createdAt: new Date(),
|
|
54
|
+
})
|
|
55
|
+
.returning();
|
|
56
|
+
|
|
57
|
+
if (!row) {
|
|
58
|
+
throw new IdentityServiceError(
|
|
59
|
+
"database_error",
|
|
60
|
+
"Failed to record memory audit event"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return row;
|
|
65
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by the identity service for every failure it recognizes. Shaping
|
|
3
|
+
* it into a transport envelope is the caller's job.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* - `not_found` — no fact exists at the given id within the actor's
|
|
8
|
+
* workspace/user scope. Covers both "no such fact" and "a fact that
|
|
9
|
+
* belongs to a different user in the same workspace" — deliberately
|
|
10
|
+
* indistinguishable, so an id never reveals whose data exists behind it.
|
|
11
|
+
* - `invalid_input` — a caller-supplied argument failed a basic
|
|
12
|
+
* invariant (e.g. an empty fact id).
|
|
13
|
+
* - `database_error` — the underlying insert did not return the row it
|
|
14
|
+
* was expected to.
|
|
15
|
+
*/
|
|
16
|
+
export type IdentityServiceErrorCode =
|
|
17
|
+
"not_found" | "invalid_input" | "database_error";
|
|
18
|
+
|
|
19
|
+
export class IdentityServiceError extends Error {
|
|
20
|
+
readonly code: IdentityServiceErrorCode;
|
|
21
|
+
// The ES2020 target predates `Error.cause`; declared so `err.cause`
|
|
22
|
+
// type-checks for callers.
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
code: IdentityServiceErrorCode,
|
|
27
|
+
message: string,
|
|
28
|
+
options?: { cause?: unknown }
|
|
29
|
+
) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "IdentityServiceError";
|
|
32
|
+
this.code = code;
|
|
33
|
+
if (options?.cause !== undefined) {
|
|
34
|
+
(this as { cause?: unknown }).cause = options.cause;
|
|
35
|
+
}
|
|
36
|
+
Object.setPrototypeOf(this, IdentityServiceError.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isIdentityServiceError(
|
|
41
|
+
error: unknown
|
|
42
|
+
): error is IdentityServiceError {
|
|
43
|
+
return error instanceof IdentityServiceError;
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy-facing reads and mutations over the user identity graph: fact
|
|
3
|
+
* listing and deletion, the profile snapshot's write, full-identity
|
|
4
|
+
* export, and the memory-audit trail.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
listFacts,
|
|
9
|
+
deleteFact,
|
|
10
|
+
saveProfileSnapshot,
|
|
11
|
+
exportIdentity,
|
|
12
|
+
getAuditTrail,
|
|
13
|
+
} from "./service";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
IdentityServiceError,
|
|
17
|
+
isIdentityServiceError,
|
|
18
|
+
type IdentityServiceErrorCode,
|
|
19
|
+
} from "./errors";
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
IdentityActor,
|
|
23
|
+
IdentityExport,
|
|
24
|
+
SaveProfileSnapshotInput,
|
|
25
|
+
UserFact,
|
|
26
|
+
UserMemory,
|
|
27
|
+
UserProfileSnapshot,
|
|
28
|
+
UserMemoryAuditRow,
|
|
29
|
+
} from "./types";
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only reads and mutations over the user identity graph: fact
|
|
3
|
+
* listing and deletion, the profile snapshot's write, a full data export,
|
|
4
|
+
* and the memory-audit trail.
|
|
5
|
+
*
|
|
6
|
+
* `deleteFact` does not touch the cached profile snapshot; profile synthesis
|
|
7
|
+
* is the product's AI code, so a caller that owns it re-triggers it and
|
|
8
|
+
* stores the result with `saveProfileSnapshot`.
|
|
9
|
+
*
|
|
10
|
+
* Callers pass a resolved actor (workspaceId, userId); core cannot depend on
|
|
11
|
+
* `@intelligo-dev/auth`. Every query filters by both ids. Every mutation
|
|
12
|
+
* writes a `user_memory_audit` row. Failures throw `IdentityServiceError`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { and, desc, eq, sql } from "drizzle-orm";
|
|
16
|
+
import { db } from "../db";
|
|
17
|
+
import {
|
|
18
|
+
userFacts,
|
|
19
|
+
userMemories,
|
|
20
|
+
userProfileSnapshots,
|
|
21
|
+
userMemoryAudit,
|
|
22
|
+
} from "../db/schema";
|
|
23
|
+
import type {
|
|
24
|
+
UserFact,
|
|
25
|
+
UserMemoryAuditRow,
|
|
26
|
+
UserProfileSnapshot,
|
|
27
|
+
} from "../db/schema";
|
|
28
|
+
import { recordMemoryAudit } from "./audit";
|
|
29
|
+
import { IdentityServiceError } from "./errors";
|
|
30
|
+
import type {
|
|
31
|
+
IdentityActor,
|
|
32
|
+
IdentityExport,
|
|
33
|
+
SaveProfileSnapshotInput,
|
|
34
|
+
} from "./types";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the fact if the actor owns it. Throws
|
|
38
|
+
* IdentityServiceError("not_found") otherwise — another user's fact is
|
|
39
|
+
* indistinguishable from a missing one.
|
|
40
|
+
*/
|
|
41
|
+
async function verifyFact(
|
|
42
|
+
actor: IdentityActor,
|
|
43
|
+
factId: string
|
|
44
|
+
): Promise<UserFact> {
|
|
45
|
+
const [fact] = await db
|
|
46
|
+
.select()
|
|
47
|
+
.from(userFacts)
|
|
48
|
+
.where(eq(userFacts.id, factId))
|
|
49
|
+
.limit(1);
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
!fact ||
|
|
53
|
+
fact.userId !== actor.userId ||
|
|
54
|
+
fact.workspaceId !== actor.workspaceId
|
|
55
|
+
) {
|
|
56
|
+
throw new IdentityServiceError("not_found", "Fact not found");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return fact;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** List the actor's facts, most important and most confident first. */
|
|
63
|
+
export async function listFacts(actor: IdentityActor): Promise<UserFact[]> {
|
|
64
|
+
return db
|
|
65
|
+
.select()
|
|
66
|
+
.from(userFacts)
|
|
67
|
+
.where(
|
|
68
|
+
and(
|
|
69
|
+
eq(userFacts.userId, actor.userId),
|
|
70
|
+
eq(userFacts.workspaceId, actor.workspaceId)
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
.orderBy(desc(userFacts.importance), desc(userFacts.confidence));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const MAX_AUDIT_LIMIT = 200;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read the actor's memory-audit trail, most recent first. Optionally
|
|
80
|
+
* scoped to a single target (e.g. one fact's history).
|
|
81
|
+
*/
|
|
82
|
+
export async function getAuditTrail(
|
|
83
|
+
actor: IdentityActor,
|
|
84
|
+
options?: { limit?: number; targetId?: string }
|
|
85
|
+
): Promise<UserMemoryAuditRow[]> {
|
|
86
|
+
const limit = Math.min(
|
|
87
|
+
Math.max(1, options?.limit ?? MAX_AUDIT_LIMIT),
|
|
88
|
+
MAX_AUDIT_LIMIT
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const baseClause = and(
|
|
92
|
+
eq(userMemoryAudit.userId, actor.userId),
|
|
93
|
+
eq(userMemoryAudit.workspaceId, actor.workspaceId)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return db
|
|
97
|
+
.select()
|
|
98
|
+
.from(userMemoryAudit)
|
|
99
|
+
.where(
|
|
100
|
+
options?.targetId
|
|
101
|
+
? and(baseClause, eq(userMemoryAudit.targetId, options.targetId))
|
|
102
|
+
: baseClause
|
|
103
|
+
)
|
|
104
|
+
.orderBy(desc(userMemoryAudit.createdAt))
|
|
105
|
+
.limit(limit);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Delete a fact and record the deletion in the audit trail. Throws
|
|
110
|
+
* `invalid_input` for an empty id and `not_found` for anything outside
|
|
111
|
+
* the actor's scope. Does not re-trigger profile synthesis.
|
|
112
|
+
*/
|
|
113
|
+
export async function deleteFact(
|
|
114
|
+
actor: IdentityActor,
|
|
115
|
+
factId: string
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
if (!factId || factId.trim().length === 0) {
|
|
118
|
+
throw new IdentityServiceError("invalid_input", "factId is required");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const existing = await verifyFact(actor, factId);
|
|
122
|
+
|
|
123
|
+
await db.transaction(async (tx) => {
|
|
124
|
+
await tx.delete(userFacts).where(eq(userFacts.id, factId));
|
|
125
|
+
await recordMemoryAudit(
|
|
126
|
+
{
|
|
127
|
+
userId: actor.userId,
|
|
128
|
+
workspaceId: actor.workspaceId,
|
|
129
|
+
targetKind: "fact",
|
|
130
|
+
targetId: factId,
|
|
131
|
+
action: "delete",
|
|
132
|
+
actorKind: "user",
|
|
133
|
+
actorId: actor.userId,
|
|
134
|
+
beforeValue: existing,
|
|
135
|
+
reason: "user requested deletion",
|
|
136
|
+
},
|
|
137
|
+
tx
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The actor's full identity graph — facts, memories, latest profile
|
|
144
|
+
* snapshot and audit trail — for a "download my data" flow. Records its own
|
|
145
|
+
* audit row (action "export").
|
|
146
|
+
*/
|
|
147
|
+
export async function exportIdentity(
|
|
148
|
+
actor: IdentityActor
|
|
149
|
+
): Promise<IdentityExport> {
|
|
150
|
+
// Recorded before the reads so the export's own audit row is part of
|
|
151
|
+
// the export — the trail a user downloads should show that download.
|
|
152
|
+
await recordMemoryAudit({
|
|
153
|
+
userId: actor.userId,
|
|
154
|
+
workspaceId: actor.workspaceId,
|
|
155
|
+
targetKind: "snapshot",
|
|
156
|
+
targetId: actor.userId,
|
|
157
|
+
action: "export",
|
|
158
|
+
actorKind: "user",
|
|
159
|
+
actorId: actor.userId,
|
|
160
|
+
reason: "user requested data export",
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const [facts, memories, snapshotRows, audit] = await Promise.all([
|
|
164
|
+
db
|
|
165
|
+
.select()
|
|
166
|
+
.from(userFacts)
|
|
167
|
+
.where(
|
|
168
|
+
and(
|
|
169
|
+
eq(userFacts.userId, actor.userId),
|
|
170
|
+
eq(userFacts.workspaceId, actor.workspaceId)
|
|
171
|
+
)
|
|
172
|
+
),
|
|
173
|
+
db
|
|
174
|
+
.select()
|
|
175
|
+
.from(userMemories)
|
|
176
|
+
.where(
|
|
177
|
+
and(
|
|
178
|
+
eq(userMemories.userId, actor.userId),
|
|
179
|
+
eq(userMemories.workspaceId, actor.workspaceId)
|
|
180
|
+
)
|
|
181
|
+
),
|
|
182
|
+
db
|
|
183
|
+
.select()
|
|
184
|
+
.from(userProfileSnapshots)
|
|
185
|
+
.where(
|
|
186
|
+
and(
|
|
187
|
+
eq(userProfileSnapshots.userId, actor.userId),
|
|
188
|
+
eq(userProfileSnapshots.workspaceId, actor.workspaceId)
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
.limit(1),
|
|
192
|
+
db
|
|
193
|
+
.select()
|
|
194
|
+
.from(userMemoryAudit)
|
|
195
|
+
.where(
|
|
196
|
+
and(
|
|
197
|
+
eq(userMemoryAudit.userId, actor.userId),
|
|
198
|
+
eq(userMemoryAudit.workspaceId, actor.workspaceId)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
.orderBy(desc(userMemoryAudit.createdAt)),
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
exportedAt: new Date().toISOString(),
|
|
206
|
+
user: { id: actor.userId, workspaceId: actor.workspaceId },
|
|
207
|
+
facts,
|
|
208
|
+
memories,
|
|
209
|
+
snapshot: snapshotRows[0] ?? null,
|
|
210
|
+
audit,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Store the actor's synthesized profile — one row per user per workspace,
|
|
216
|
+
* inserted on the first synthesis and replaced on every later one. The
|
|
217
|
+
* version is bumped in the same statement (`ON CONFLICT … version + 1`),
|
|
218
|
+
* so two syntheses racing each other get two versions rather than one
|
|
219
|
+
* overwriting the other's number; it starts at 1.
|
|
220
|
+
*
|
|
221
|
+
* A field left `undefined` keeps what the row holds (null on insert); an
|
|
222
|
+
* explicit `null` clears it. `synthesizedAt` defaults to now. Records an
|
|
223
|
+
* audit row (`snapshot`, `create` or `update`) in the same transaction,
|
|
224
|
+
* attributed to `input.actorKind` — `system_job` unless the caller says
|
|
225
|
+
* otherwise. Throws `invalid_input` for an empty summary.
|
|
226
|
+
*/
|
|
227
|
+
export async function saveProfileSnapshot(
|
|
228
|
+
actor: IdentityActor,
|
|
229
|
+
input: SaveProfileSnapshotInput
|
|
230
|
+
): Promise<UserProfileSnapshot> {
|
|
231
|
+
if (!input.summary || input.summary.trim().length === 0) {
|
|
232
|
+
throw new IdentityServiceError("invalid_input", "summary is required");
|
|
233
|
+
}
|
|
234
|
+
if (input.factsDigest === undefined || input.factsDigest === null) {
|
|
235
|
+
throw new IdentityServiceError("invalid_input", "factsDigest is required");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const replaced = {
|
|
239
|
+
summary: input.summary,
|
|
240
|
+
factsDigest: input.factsDigest,
|
|
241
|
+
synthesizedAt: input.synthesizedAt ?? new Date(),
|
|
242
|
+
...definedOnly({
|
|
243
|
+
summaryEn: input.summaryEn,
|
|
244
|
+
summaryMn: input.summaryMn,
|
|
245
|
+
activeGoals: input.activeGoals,
|
|
246
|
+
personalitySignals: input.personalitySignals,
|
|
247
|
+
relationshipNotes: input.relationshipNotes,
|
|
248
|
+
synthesizedByModel: input.synthesizedByModel,
|
|
249
|
+
nextSynthesisAt: input.nextSynthesisAt,
|
|
250
|
+
triggerReason: input.triggerReason,
|
|
251
|
+
}),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return db.transaction(async (tx) => {
|
|
255
|
+
const [row] = await tx
|
|
256
|
+
.insert(userProfileSnapshots)
|
|
257
|
+
.values({
|
|
258
|
+
userId: actor.userId,
|
|
259
|
+
workspaceId: actor.workspaceId,
|
|
260
|
+
version: 1,
|
|
261
|
+
...replaced,
|
|
262
|
+
})
|
|
263
|
+
.onConflictDoUpdate({
|
|
264
|
+
target: [userProfileSnapshots.userId, userProfileSnapshots.workspaceId],
|
|
265
|
+
set: {
|
|
266
|
+
...replaced,
|
|
267
|
+
version: sql`${userProfileSnapshots.version} + 1`,
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
.returning();
|
|
271
|
+
|
|
272
|
+
if (!row) {
|
|
273
|
+
throw new IdentityServiceError(
|
|
274
|
+
"database_error",
|
|
275
|
+
"Failed to save profile snapshot"
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await recordMemoryAudit(
|
|
280
|
+
{
|
|
281
|
+
userId: actor.userId,
|
|
282
|
+
workspaceId: actor.workspaceId,
|
|
283
|
+
targetKind: "snapshot",
|
|
284
|
+
targetId: actor.userId,
|
|
285
|
+
action: row.version === 1 ? "create" : "update",
|
|
286
|
+
actorKind: input.actorKind ?? "system_job",
|
|
287
|
+
actorId: input.actorId,
|
|
288
|
+
afterValue: { version: row.version, triggerReason: row.triggerReason },
|
|
289
|
+
reason: input.reason,
|
|
290
|
+
},
|
|
291
|
+
tx
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
return row;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** The entries whose value is not `undefined` — `null` is kept, to clear. */
|
|
299
|
+
function definedOnly<T extends Record<string, unknown>>(fields: T): Partial<T> {
|
|
300
|
+
return Object.fromEntries(
|
|
301
|
+
Object.entries(fields).filter(([, value]) => value !== undefined)
|
|
302
|
+
) as Partial<T>;
|
|
303
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuditActorKind,
|
|
3
|
+
SynthesisTriggerReason,
|
|
4
|
+
UserFact,
|
|
5
|
+
UserMemory,
|
|
6
|
+
UserProfileSnapshot,
|
|
7
|
+
UserMemoryAuditRow,
|
|
8
|
+
} from "../db/schema";
|
|
9
|
+
|
|
10
|
+
export type { UserFact, UserMemory, UserProfileSnapshot, UserMemoryAuditRow };
|
|
11
|
+
|
|
12
|
+
/** Resolved actor identity — every query is scoped to this pair. */
|
|
13
|
+
export type IdentityActor = {
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Full identity dump for a GDPR-style "download my data" flow: every
|
|
20
|
+
* fact and memory the actor owns, their latest synthesized profile
|
|
21
|
+
* snapshot (if one exists), and their complete memory-audit trail.
|
|
22
|
+
*/
|
|
23
|
+
export type IdentityExport = {
|
|
24
|
+
exportedAt: string;
|
|
25
|
+
user: { id: string; workspaceId: string };
|
|
26
|
+
facts: UserFact[];
|
|
27
|
+
memories: UserMemory[];
|
|
28
|
+
snapshot: UserProfileSnapshot | null;
|
|
29
|
+
audit: UserMemoryAuditRow[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A synthesized profile, as `saveProfileSnapshot` stores it. The actor
|
|
34
|
+
* supplies the tenancy; the version is the store's.
|
|
35
|
+
*/
|
|
36
|
+
export type SaveProfileSnapshotInput = {
|
|
37
|
+
/** The summary a prompt hydrates from, in the product's primary language. */
|
|
38
|
+
summary: string;
|
|
39
|
+
summaryEn?: string | null;
|
|
40
|
+
summaryMn?: string | null;
|
|
41
|
+
/** The structured digest the summary was written from. */
|
|
42
|
+
factsDigest: unknown;
|
|
43
|
+
activeGoals?: unknown;
|
|
44
|
+
personalitySignals?: unknown;
|
|
45
|
+
relationshipNotes?: unknown;
|
|
46
|
+
/** The model that wrote it, or a label for a deterministic synthesis. */
|
|
47
|
+
synthesizedByModel?: string | null;
|
|
48
|
+
/** Default: now. */
|
|
49
|
+
synthesizedAt?: Date;
|
|
50
|
+
/** When a scheduled re-synthesis may next pick this user up. */
|
|
51
|
+
nextSynthesisAt?: Date | null;
|
|
52
|
+
triggerReason?: SynthesisTriggerReason | null;
|
|
53
|
+
/** Who the audit row names. Default `"system_job"`. */
|
|
54
|
+
actorKind?: AuditActorKind;
|
|
55
|
+
actorId?: string;
|
|
56
|
+
/** Recorded on the audit row. */
|
|
57
|
+
reason?: string;
|
|
58
|
+
};
|
package/src/index.ts
ADDED