@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.
Files changed (356) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +102 -0
  3. package/dist/attachments/errors.d.ts +7 -0
  4. package/dist/attachments/errors.d.ts.map +1 -0
  5. package/dist/attachments/errors.js +11 -0
  6. package/dist/attachments/errors.js.map +1 -0
  7. package/dist/attachments/index.d.ts +8 -0
  8. package/dist/attachments/index.d.ts.map +1 -0
  9. package/dist/attachments/index.js +7 -0
  10. package/dist/attachments/index.js.map +1 -0
  11. package/dist/attachments/service.d.ts +45 -0
  12. package/dist/attachments/service.d.ts.map +1 -0
  13. package/dist/attachments/service.js +100 -0
  14. package/dist/attachments/service.js.map +1 -0
  15. package/dist/attachments/types.d.ts +6 -0
  16. package/dist/attachments/types.d.ts.map +1 -0
  17. package/dist/attachments/types.js +2 -0
  18. package/dist/attachments/types.js.map +1 -0
  19. package/dist/conversations/errors.d.ts +2 -8
  20. package/dist/conversations/errors.d.ts.map +1 -1
  21. package/dist/conversations/errors.js +2 -8
  22. package/dist/conversations/errors.js.map +1 -1
  23. package/dist/conversations/index.d.ts +4 -12
  24. package/dist/conversations/index.d.ts.map +1 -1
  25. package/dist/conversations/index.js +2 -11
  26. package/dist/conversations/index.js.map +1 -1
  27. package/dist/conversations/service.d.ts +49 -61
  28. package/dist/conversations/service.d.ts.map +1 -1
  29. package/dist/conversations/service.js +110 -83
  30. package/dist/conversations/service.js.map +1 -1
  31. package/dist/conversations/types.d.ts +1 -4
  32. package/dist/conversations/types.d.ts.map +1 -1
  33. package/dist/conversations/types.js +0 -3
  34. package/dist/conversations/types.js.map +1 -1
  35. package/dist/db/client.d.ts +21 -10
  36. package/dist/db/client.d.ts.map +1 -1
  37. package/dist/db/client.js +43 -18
  38. package/dist/db/client.js.map +1 -1
  39. package/dist/db/index.d.ts +3 -3
  40. package/dist/db/index.d.ts.map +1 -1
  41. package/dist/db/index.js +3 -7
  42. package/dist/db/index.js.map +1 -1
  43. package/dist/db/schema/agents.d.ts +14 -51
  44. package/dist/db/schema/agents.d.ts.map +1 -1
  45. package/dist/db/schema/agents.js +9 -21
  46. package/dist/db/schema/agents.js.map +1 -1
  47. package/dist/db/schema/ai.d.ts +86 -1556
  48. package/dist/db/schema/ai.d.ts.map +1 -1
  49. package/dist/db/schema/ai.js +32 -236
  50. package/dist/db/schema/ai.js.map +1 -1
  51. package/dist/db/schema/auth.d.ts +5 -34
  52. package/dist/db/schema/auth.d.ts.map +1 -1
  53. package/dist/db/schema/auth.js +25 -56
  54. package/dist/db/schema/auth.js.map +1 -1
  55. package/dist/db/schema/billing.d.ts +339 -105
  56. package/dist/db/schema/billing.d.ts.map +1 -1
  57. package/dist/db/schema/billing.js +99 -58
  58. package/dist/db/schema/billing.js.map +1 -1
  59. package/dist/db/schema/feature-flags.d.ts +3 -12
  60. package/dist/db/schema/feature-flags.d.ts.map +1 -1
  61. package/dist/db/schema/feature-flags.js +3 -12
  62. package/dist/db/schema/feature-flags.js.map +1 -1
  63. package/dist/db/schema/identity.d.ts +12 -274
  64. package/dist/db/schema/identity.d.ts.map +1 -1
  65. package/dist/db/schema/identity.js +19 -104
  66. package/dist/db/schema/identity.js.map +1 -1
  67. package/dist/db/schema/index.d.ts +8 -15
  68. package/dist/db/schema/index.d.ts.map +1 -1
  69. package/dist/db/schema/index.js +12 -42
  70. package/dist/db/schema/index.js.map +1 -1
  71. package/dist/db/schema/notifications.d.ts +3 -17
  72. package/dist/db/schema/notifications.d.ts.map +1 -1
  73. package/dist/db/schema/notifications.js +3 -17
  74. package/dist/db/schema/notifications.js.map +1 -1
  75. package/dist/db/schema/usage.d.ts +94 -77
  76. package/dist/db/schema/usage.d.ts.map +1 -1
  77. package/dist/db/schema/usage.js +59 -89
  78. package/dist/db/schema/usage.js.map +1 -1
  79. package/dist/db/validate.d.ts +2 -11
  80. package/dist/db/validate.d.ts.map +1 -1
  81. package/dist/db/validate.js +6 -17
  82. package/dist/db/validate.js.map +1 -1
  83. package/dist/db/workspace-queries.d.ts +5 -29
  84. package/dist/db/workspace-queries.d.ts.map +1 -1
  85. package/dist/db/workspace-queries.js +5 -29
  86. package/dist/db/workspace-queries.js.map +1 -1
  87. package/dist/documents/classifier.d.ts +5 -12
  88. package/dist/documents/classifier.d.ts.map +1 -1
  89. package/dist/documents/classifier.js +16 -16
  90. package/dist/documents/classifier.js.map +1 -1
  91. package/dist/documents/errors.d.ts +2 -10
  92. package/dist/documents/errors.d.ts.map +1 -1
  93. package/dist/documents/errors.js +2 -10
  94. package/dist/documents/errors.js.map +1 -1
  95. package/dist/documents/index.d.ts +4 -13
  96. package/dist/documents/index.d.ts.map +1 -1
  97. package/dist/documents/index.js +3 -12
  98. package/dist/documents/index.js.map +1 -1
  99. package/dist/documents/service.d.ts +19 -26
  100. package/dist/documents/service.d.ts.map +1 -1
  101. package/dist/documents/service.js +44 -38
  102. package/dist/documents/service.js.map +1 -1
  103. package/dist/documents/types.d.ts +6 -10
  104. package/dist/documents/types.d.ts.map +1 -1
  105. package/dist/documents/types.js +0 -3
  106. package/dist/documents/types.js.map +1 -1
  107. package/dist/email/brand.d.ts +17 -0
  108. package/dist/email/brand.d.ts.map +1 -0
  109. package/dist/email/brand.js +9 -0
  110. package/dist/email/brand.js.map +1 -0
  111. package/dist/email/index.d.ts +4 -4
  112. package/dist/email/index.js +4 -4
  113. package/dist/email/provider.d.ts +1 -7
  114. package/dist/email/provider.d.ts.map +1 -1
  115. package/dist/email/provider.js +10 -19
  116. package/dist/email/provider.js.map +1 -1
  117. package/dist/email/send.d.ts +12 -12
  118. package/dist/email/send.d.ts.map +1 -1
  119. package/dist/email/send.js +5 -19
  120. package/dist/email/send.js.map +1 -1
  121. package/dist/email/senders.d.ts +17 -30
  122. package/dist/email/senders.d.ts.map +1 -1
  123. package/dist/email/senders.js +33 -47
  124. package/dist/email/senders.js.map +1 -1
  125. package/dist/email/templates/base-layout.d.ts +1 -1
  126. package/dist/email/templates/base-layout.d.ts.map +1 -1
  127. package/dist/email/templates/base-layout.js +4 -6
  128. package/dist/email/templates/base-layout.js.map +1 -1
  129. package/dist/email/templates/index.d.ts +10 -10
  130. package/dist/email/templates/index.d.ts.map +1 -1
  131. package/dist/email/templates/index.js +10 -16
  132. package/dist/email/templates/index.js.map +1 -1
  133. package/dist/email/templates/password-reset.d.ts +2 -2
  134. package/dist/email/templates/password-reset.d.ts.map +1 -1
  135. package/dist/email/templates/password-reset.js +10 -22
  136. package/dist/email/templates/password-reset.js.map +1 -1
  137. package/dist/email/templates/payment-failed.d.ts +2 -2
  138. package/dist/email/templates/payment-failed.d.ts.map +1 -1
  139. package/dist/email/templates/payment-failed.js +11 -24
  140. package/dist/email/templates/payment-failed.js.map +1 -1
  141. package/dist/email/templates/quota-warning.d.ts +4 -4
  142. package/dist/email/templates/quota-warning.d.ts.map +1 -1
  143. package/dist/email/templates/quota-warning.js +19 -39
  144. package/dist/email/templates/quota-warning.js.map +1 -1
  145. package/dist/email/templates/subscription-confirmed.d.ts +2 -2
  146. package/dist/email/templates/subscription-confirmed.d.ts.map +1 -1
  147. package/dist/email/templates/subscription-confirmed.js +13 -28
  148. package/dist/email/templates/subscription-confirmed.js.map +1 -1
  149. package/dist/email/templates/trial-expiry.d.ts +2 -2
  150. package/dist/email/templates/trial-expiry.d.ts.map +1 -1
  151. package/dist/email/templates/trial-expiry.js +12 -36
  152. package/dist/email/templates/trial-expiry.js.map +1 -1
  153. package/dist/email/templates/trial-warning.d.ts +2 -2
  154. package/dist/email/templates/trial-warning.d.ts.map +1 -1
  155. package/dist/email/templates/trial-warning.js +15 -32
  156. package/dist/email/templates/trial-warning.js.map +1 -1
  157. package/dist/email/templates/verify-email.d.ts +2 -2
  158. package/dist/email/templates/verify-email.d.ts.map +1 -1
  159. package/dist/email/templates/verify-email.js +10 -22
  160. package/dist/email/templates/verify-email.js.map +1 -1
  161. package/dist/email/templates/welcome.d.ts +2 -2
  162. package/dist/email/templates/welcome.d.ts.map +1 -1
  163. package/dist/email/templates/welcome.js +13 -20
  164. package/dist/email/templates/welcome.js.map +1 -1
  165. package/dist/email/templates/workspace-invitation.d.ts +2 -2
  166. package/dist/email/templates/workspace-invitation.d.ts.map +1 -1
  167. package/dist/email/templates/workspace-invitation.js +12 -26
  168. package/dist/email/templates/workspace-invitation.js.map +1 -1
  169. package/dist/env.d.ts +5 -10
  170. package/dist/env.d.ts.map +1 -1
  171. package/dist/env.js +41 -31
  172. package/dist/env.js.map +1 -1
  173. package/dist/identity/audit.d.ts +11 -24
  174. package/dist/identity/audit.d.ts.map +1 -1
  175. package/dist/identity/audit.js +13 -27
  176. package/dist/identity/audit.js.map +1 -1
  177. package/dist/identity/errors.d.ts +4 -12
  178. package/dist/identity/errors.d.ts.map +1 -1
  179. package/dist/identity/errors.js +2 -8
  180. package/dist/identity/errors.js.map +1 -1
  181. package/dist/identity/index.d.ts +6 -13
  182. package/dist/identity/index.d.ts.map +1 -1
  183. package/dist/identity/index.js +5 -12
  184. package/dist/identity/index.js.map +1 -1
  185. package/dist/identity/service.d.ts +29 -47
  186. package/dist/identity/service.d.ts.map +1 -1
  187. package/dist/identity/service.js +107 -79
  188. package/dist/identity/service.js.map +1 -1
  189. package/dist/identity/types.d.ts +28 -4
  190. package/dist/identity/types.d.ts.map +1 -1
  191. package/dist/identity/types.js +0 -3
  192. package/dist/identity/types.js.map +1 -1
  193. package/dist/index.d.ts +1 -2
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +3 -10
  196. package/dist/index.js.map +1 -1
  197. package/dist/logger.d.ts +7 -18
  198. package/dist/logger.d.ts.map +1 -1
  199. package/dist/logger.js +45 -49
  200. package/dist/logger.js.map +1 -1
  201. package/dist/money.d.ts +95 -0
  202. package/dist/money.d.ts.map +1 -0
  203. package/dist/money.js +208 -0
  204. package/dist/money.js.map +1 -0
  205. package/dist/notifications/index.d.ts +9 -43
  206. package/dist/notifications/index.d.ts.map +1 -1
  207. package/dist/notifications/index.js +9 -49
  208. package/dist/notifications/index.js.map +1 -1
  209. package/dist/notifications/triggers.d.ts +9 -32
  210. package/dist/notifications/triggers.d.ts.map +1 -1
  211. package/dist/notifications/triggers.js +21 -58
  212. package/dist/notifications/triggers.js.map +1 -1
  213. package/dist/notifications/types.d.ts +6 -8
  214. package/dist/notifications/types.d.ts.map +1 -1
  215. package/dist/notifications/types.js +0 -9
  216. package/dist/notifications/types.js.map +1 -1
  217. package/dist/prompt.d.ts +38 -0
  218. package/dist/prompt.d.ts.map +1 -0
  219. package/dist/prompt.js +118 -0
  220. package/dist/prompt.js.map +1 -0
  221. package/dist/registry.d.ts +32 -0
  222. package/dist/registry.d.ts.map +1 -0
  223. package/dist/registry.js +60 -0
  224. package/dist/registry.js.map +1 -0
  225. package/dist/request-context.d.ts +49 -0
  226. package/dist/request-context.d.ts.map +1 -0
  227. package/dist/request-context.js +100 -0
  228. package/dist/request-context.js.map +1 -0
  229. package/dist/storage/index.d.ts +65 -0
  230. package/dist/storage/index.d.ts.map +1 -0
  231. package/dist/storage/index.js +109 -0
  232. package/dist/storage/index.js.map +1 -0
  233. package/package.json +74 -24
  234. package/src/attachments/errors.ts +18 -0
  235. package/src/attachments/index.ts +22 -0
  236. package/src/attachments/service.ts +178 -0
  237. package/src/attachments/types.ts +6 -0
  238. package/src/conversations/errors.ts +45 -0
  239. package/src/conversations/index.ts +39 -0
  240. package/src/conversations/service.ts +594 -0
  241. package/src/conversations/types.ts +15 -0
  242. package/src/db/client.ts +91 -0
  243. package/src/db/index.ts +3 -0
  244. package/src/db/migrations/0000_baseline.sql +652 -0
  245. package/src/db/migrations/0001_reconcile_chain_built_databases.sql +53 -0
  246. package/src/db/migrations/0002_webhook_claim_lease.sql +1 -0
  247. package/src/db/migrations/0003_local_payments.sql +18 -0
  248. package/src/db/migrations/README.md +103 -36
  249. package/src/db/migrations/legacy-chain.json +206 -0
  250. package/src/db/migrations/meta/0000_snapshot.json +4182 -157
  251. package/src/db/migrations/meta/0002_snapshot.json +2478 -368
  252. package/src/db/migrations/meta/0003_snapshot.json +2567 -554
  253. package/src/db/migrations/meta/_journal.json +8 -274
  254. package/src/db/schema/agents.ts +57 -0
  255. package/src/db/schema/ai.ts +174 -0
  256. package/src/db/schema/auth.ts +183 -0
  257. package/src/db/schema/billing.ts +245 -0
  258. package/src/db/schema/feature-flags.ts +20 -0
  259. package/src/db/schema/identity.ts +250 -0
  260. package/src/db/schema/index.ts +12 -0
  261. package/src/db/schema/notifications.ts +31 -0
  262. package/src/db/schema/usage.ts +269 -0
  263. package/src/db/validate.ts +63 -0
  264. package/src/db/workspace-queries.ts +64 -0
  265. package/src/documents/classifier.ts +61 -0
  266. package/src/documents/errors.ts +44 -0
  267. package/src/documents/index.ts +24 -0
  268. package/src/documents/service.ts +256 -0
  269. package/src/documents/types.ts +24 -0
  270. package/src/email/brand.ts +24 -0
  271. package/src/email/index.ts +19 -0
  272. package/src/email/provider.ts +278 -0
  273. package/src/email/send.ts +169 -0
  274. package/src/email/senders.ts +292 -0
  275. package/src/email/templates/base-layout.tsx +145 -0
  276. package/src/email/templates/index.ts +28 -0
  277. package/src/email/templates/password-reset.tsx +72 -0
  278. package/src/email/templates/payment-failed.tsx +77 -0
  279. package/src/email/templates/quota-warning.tsx +118 -0
  280. package/src/email/templates/subscription-confirmed.tsx +91 -0
  281. package/src/email/templates/trial-expiry.tsx +124 -0
  282. package/src/email/templates/trial-warning.tsx +104 -0
  283. package/src/email/templates/verify-email.tsx +72 -0
  284. package/src/email/templates/welcome.tsx +64 -0
  285. package/src/email/templates/workspace-invitation.tsx +105 -0
  286. package/src/env.ts +180 -0
  287. package/src/identity/audit.ts +65 -0
  288. package/src/identity/errors.ts +44 -0
  289. package/src/identity/index.ts +29 -0
  290. package/src/identity/service.ts +303 -0
  291. package/src/identity/types.ts +58 -0
  292. package/src/index.ts +4 -0
  293. package/src/logger.ts +198 -0
  294. package/src/money.ts +270 -0
  295. package/src/notifications/index.ts +112 -0
  296. package/src/notifications/triggers.ts +158 -0
  297. package/src/notifications/types.ts +26 -0
  298. package/src/prompt.ts +142 -0
  299. package/src/registry.ts +75 -0
  300. package/src/request-context.ts +137 -0
  301. package/src/storage/index.ts +153 -0
  302. package/dist/db/schema/rag.d.ts +0 -171
  303. package/dist/db/schema/rag.d.ts.map +0 -1
  304. package/dist/db/schema/rag.js +0 -42
  305. package/dist/db/schema/rag.js.map +0 -1
  306. package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
  307. package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
  308. package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
  309. package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
  310. package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
  311. package/src/db/migrations/0005_careful_switch.sql +0 -8
  312. package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
  313. package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
  314. package/src/db/migrations/0008_powerful_scream.sql +0 -39
  315. package/src/db/migrations/0009_sad_ink.sql +0 -1
  316. package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
  317. package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
  318. package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
  319. package/src/db/migrations/0012_user_profiles.sql +0 -30
  320. package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
  321. package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
  322. package/src/db/migrations/0015_new_v1_tables.sql +0 -149
  323. package/src/db/migrations/0016_document_types.sql +0 -30
  324. package/src/db/migrations/0017_document_metadata.sql +0 -2
  325. package/src/db/migrations/0018_identity_graph.sql +0 -200
  326. package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
  327. package/src/db/migrations/0020_pending_extractions.sql +0 -35
  328. package/src/db/migrations/0021_rag_documents.sql +0 -39
  329. package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
  330. package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
  331. package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
  332. package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
  333. package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
  334. package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
  335. package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
  336. package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
  337. package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
  338. package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
  339. package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
  340. package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
  341. package/src/db/migrations/0034_rate_limit_count.sql +0 -9
  342. package/src/db/migrations/0035_credit_reservations.sql +0 -24
  343. package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
  344. package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
  345. package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
  346. package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
  347. package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
  348. package/src/db/migrations/meta/0001_snapshot.json +0 -631
  349. package/src/db/migrations/meta/0004_snapshot.json +0 -2500
  350. package/src/db/migrations/meta/0005_snapshot.json +0 -2548
  351. package/src/db/migrations/meta/0006_snapshot.json +0 -2650
  352. package/src/db/migrations/meta/0007_snapshot.json +0 -2657
  353. package/src/db/migrations/meta/0008_snapshot.json +0 -2719
  354. package/src/db/migrations/meta/0009_snapshot.json +0 -2726
  355. package/src/db/migrations/meta/0010_snapshot.json +0 -2739
  356. 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
@@ -0,0 +1,4 @@
1
+ export * from "./db";
2
+
3
+ // Everything else is a subpath import: @intelligo-dev/core/email,
4
+ // /notifications, /conversations, /documents, /identity, /logger, /env.