@intelligo-dev/core 1.0.0-beta.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.
Files changed (254) hide show
  1. package/LICENSE +201 -0
  2. package/dist/conversations/errors.d.ts +31 -0
  3. package/dist/conversations/errors.d.ts.map +1 -0
  4. package/dist/conversations/errors.js +25 -0
  5. package/dist/conversations/errors.js.map +1 -0
  6. package/dist/conversations/index.d.ts +13 -0
  7. package/dist/conversations/index.d.ts.map +1 -0
  8. package/dist/conversations/index.js +12 -0
  9. package/dist/conversations/index.js.map +1 -0
  10. package/dist/conversations/service.d.ts +132 -0
  11. package/dist/conversations/service.d.ts.map +1 -0
  12. package/dist/conversations/service.js +347 -0
  13. package/dist/conversations/service.js.map +1 -0
  14. package/dist/conversations/types.d.ts +10 -0
  15. package/dist/conversations/types.d.ts.map +1 -0
  16. package/dist/conversations/types.js +5 -0
  17. package/dist/conversations/types.js.map +1 -0
  18. package/dist/db/client.d.ts +19 -0
  19. package/dist/db/client.d.ts.map +1 -0
  20. package/dist/db/client.js +52 -0
  21. package/dist/db/client.js.map +1 -0
  22. package/dist/db/index.d.ts +4 -0
  23. package/dist/db/index.d.ts.map +1 -0
  24. package/dist/db/index.js +8 -0
  25. package/dist/db/index.js.map +1 -0
  26. package/dist/db/schema/agents.d.ts +400 -0
  27. package/dist/db/schema/agents.d.ts.map +1 -0
  28. package/dist/db/schema/agents.js +39 -0
  29. package/dist/db/schema/agents.js.map +1 -0
  30. package/dist/db/schema/ai.d.ts +2222 -0
  31. package/dist/db/schema/ai.d.ts.map +1 -0
  32. package/dist/db/schema/ai.js +313 -0
  33. package/dist/db/schema/ai.js.map +1 -0
  34. package/dist/db/schema/auth.d.ts +1222 -0
  35. package/dist/db/schema/auth.d.ts.map +1 -0
  36. package/dist/db/schema/auth.js +162 -0
  37. package/dist/db/schema/auth.js.map +1 -0
  38. package/dist/db/schema/billing.d.ts +1199 -0
  39. package/dist/db/schema/billing.d.ts.map +1 -0
  40. package/dist/db/schema/billing.js +150 -0
  41. package/dist/db/schema/billing.js.map +1 -0
  42. package/dist/db/schema/feature-flags.d.ts +143 -0
  43. package/dist/db/schema/feature-flags.d.ts.map +1 -0
  44. package/dist/db/schema/feature-flags.js +25 -0
  45. package/dist/db/schema/feature-flags.js.map +1 -0
  46. package/dist/db/schema/identity.d.ts +1295 -0
  47. package/dist/db/schema/identity.d.ts.map +1 -0
  48. package/dist/db/schema/identity.js +258 -0
  49. package/dist/db/schema/identity.js.map +1 -0
  50. package/dist/db/schema/index.d.ts +16 -0
  51. package/dist/db/schema/index.d.ts.map +1 -0
  52. package/dist/db/schema/index.js +43 -0
  53. package/dist/db/schema/index.js.map +1 -0
  54. package/dist/db/schema/notifications.d.ts +182 -0
  55. package/dist/db/schema/notifications.d.ts.map +1 -0
  56. package/dist/db/schema/notifications.js +38 -0
  57. package/dist/db/schema/notifications.js.map +1 -0
  58. package/dist/db/schema/rag.d.ts +171 -0
  59. package/dist/db/schema/rag.d.ts.map +1 -0
  60. package/dist/db/schema/rag.js +42 -0
  61. package/dist/db/schema/rag.js.map +1 -0
  62. package/dist/db/schema/usage.d.ts +1193 -0
  63. package/dist/db/schema/usage.d.ts.map +1 -0
  64. package/dist/db/schema/usage.js +224 -0
  65. package/dist/db/schema/usage.js.map +1 -0
  66. package/dist/db/validate.d.ts +19 -0
  67. package/dist/db/validate.d.ts.map +1 -0
  68. package/dist/db/validate.js +58 -0
  69. package/dist/db/validate.js.map +1 -0
  70. package/dist/db/workspace-queries.d.ts +75 -0
  71. package/dist/db/workspace-queries.d.ts.map +1 -0
  72. package/dist/db/workspace-queries.js +78 -0
  73. package/dist/db/workspace-queries.js.map +1 -0
  74. package/dist/documents/classifier.d.ts +31 -0
  75. package/dist/documents/classifier.d.ts.map +1 -0
  76. package/dist/documents/classifier.js +41 -0
  77. package/dist/documents/classifier.js.map +1 -0
  78. package/dist/documents/errors.d.ts +32 -0
  79. package/dist/documents/errors.d.ts.map +1 -0
  80. package/dist/documents/errors.js +27 -0
  81. package/dist/documents/errors.js.map +1 -0
  82. package/dist/documents/index.d.ts +14 -0
  83. package/dist/documents/index.d.ts.map +1 -0
  84. package/dist/documents/index.js +13 -0
  85. package/dist/documents/index.js.map +1 -0
  86. package/dist/documents/service.d.ts +54 -0
  87. package/dist/documents/service.d.ts.map +1 -0
  88. package/dist/documents/service.js +146 -0
  89. package/dist/documents/service.js.map +1 -0
  90. package/dist/documents/types.d.ts +27 -0
  91. package/dist/documents/types.d.ts.map +1 -0
  92. package/dist/documents/types.js +5 -0
  93. package/dist/documents/types.js.map +1 -0
  94. package/dist/email/index.d.ts +5 -0
  95. package/dist/email/index.d.ts.map +1 -0
  96. package/dist/email/index.js +5 -0
  97. package/dist/email/index.js.map +1 -0
  98. package/dist/email/provider.d.ts +68 -0
  99. package/dist/email/provider.d.ts.map +1 -0
  100. package/dist/email/provider.js +199 -0
  101. package/dist/email/provider.js.map +1 -0
  102. package/dist/email/send.d.ts +47 -0
  103. package/dist/email/send.d.ts.map +1 -0
  104. package/dist/email/send.js +110 -0
  105. package/dist/email/send.js.map +1 -0
  106. package/dist/email/senders.d.ts +93 -0
  107. package/dist/email/senders.d.ts.map +1 -0
  108. package/dist/email/senders.js +232 -0
  109. package/dist/email/senders.js.map +1 -0
  110. package/dist/email/templates/base-layout.d.ts +7 -0
  111. package/dist/email/templates/base-layout.d.ts.map +1 -0
  112. package/dist/email/templates/base-layout.js +69 -0
  113. package/dist/email/templates/base-layout.js.map +1 -0
  114. package/dist/email/templates/index.d.ts +11 -0
  115. package/dist/email/templates/index.d.ts.map +1 -0
  116. package/dist/email/templates/index.js +17 -0
  117. package/dist/email/templates/index.js.map +1 -0
  118. package/dist/email/templates/password-reset.d.ts +7 -0
  119. package/dist/email/templates/password-reset.d.ts.map +1 -0
  120. package/dist/email/templates/password-reset.js +59 -0
  121. package/dist/email/templates/password-reset.js.map +1 -0
  122. package/dist/email/templates/payment-failed.d.ts +8 -0
  123. package/dist/email/templates/payment-failed.d.ts.map +1 -0
  124. package/dist/email/templates/payment-failed.js +62 -0
  125. package/dist/email/templates/payment-failed.js.map +1 -0
  126. package/dist/email/templates/quota-warning.d.ts +11 -0
  127. package/dist/email/templates/quota-warning.d.ts.map +1 -0
  128. package/dist/email/templates/quota-warning.js +82 -0
  129. package/dist/email/templates/quota-warning.js.map +1 -0
  130. package/dist/email/templates/subscription-confirmed.d.ts +9 -0
  131. package/dist/email/templates/subscription-confirmed.d.ts.map +1 -0
  132. package/dist/email/templates/subscription-confirmed.js +72 -0
  133. package/dist/email/templates/subscription-confirmed.js.map +1 -0
  134. package/dist/email/templates/trial-expiry.d.ts +9 -0
  135. package/dist/email/templates/trial-expiry.d.ts.map +1 -0
  136. package/dist/email/templates/trial-expiry.js +86 -0
  137. package/dist/email/templates/trial-expiry.js.map +1 -0
  138. package/dist/email/templates/trial-warning.d.ts +11 -0
  139. package/dist/email/templates/trial-warning.d.ts.map +1 -0
  140. package/dist/email/templates/trial-warning.js +70 -0
  141. package/dist/email/templates/trial-warning.js.map +1 -0
  142. package/dist/email/templates/verify-email.d.ts +7 -0
  143. package/dist/email/templates/verify-email.d.ts.map +1 -0
  144. package/dist/email/templates/verify-email.js +59 -0
  145. package/dist/email/templates/verify-email.js.map +1 -0
  146. package/dist/email/templates/welcome.d.ts +7 -0
  147. package/dist/email/templates/welcome.d.ts.map +1 -0
  148. package/dist/email/templates/welcome.js +51 -0
  149. package/dist/email/templates/welcome.js.map +1 -0
  150. package/dist/email/templates/workspace-invitation.d.ts +10 -0
  151. package/dist/email/templates/workspace-invitation.d.ts.map +1 -0
  152. package/dist/email/templates/workspace-invitation.js +79 -0
  153. package/dist/email/templates/workspace-invitation.js.map +1 -0
  154. package/dist/env.d.ts +21 -0
  155. package/dist/env.d.ts.map +1 -0
  156. package/dist/env.js +129 -0
  157. package/dist/env.js.map +1 -0
  158. package/dist/identity/audit.d.ts +39 -0
  159. package/dist/identity/audit.d.ts.map +1 -0
  160. package/dist/identity/audit.js +51 -0
  161. package/dist/identity/audit.js.map +1 -0
  162. package/dist/identity/errors.d.ts +32 -0
  163. package/dist/identity/errors.d.ts.map +1 -0
  164. package/dist/identity/errors.js +25 -0
  165. package/dist/identity/errors.js.map +1 -0
  166. package/dist/identity/index.d.ts +16 -0
  167. package/dist/identity/index.d.ts.map +1 -0
  168. package/dist/identity/index.js +15 -0
  169. package/dist/identity/index.js.map +1 -0
  170. package/dist/identity/service.d.ts +70 -0
  171. package/dist/identity/service.d.ts.map +1 -0
  172. package/dist/identity/service.js +179 -0
  173. package/dist/identity/service.js.map +1 -0
  174. package/dist/identity/types.d.ts +27 -0
  175. package/dist/identity/types.d.ts.map +1 -0
  176. package/dist/identity/types.js +5 -0
  177. package/dist/identity/types.js.map +1 -0
  178. package/dist/index.d.ts +3 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +11 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/logger.d.ts +50 -0
  183. package/dist/logger.d.ts.map +1 -0
  184. package/dist/logger.js +183 -0
  185. package/dist/logger.js.map +1 -0
  186. package/dist/notifications/index.d.ts +86 -0
  187. package/dist/notifications/index.d.ts.map +1 -0
  188. package/dist/notifications/index.js +112 -0
  189. package/dist/notifications/index.js.map +1 -0
  190. package/dist/notifications/triggers.d.ts +68 -0
  191. package/dist/notifications/triggers.d.ts.map +1 -0
  192. package/dist/notifications/triggers.js +145 -0
  193. package/dist/notifications/triggers.js.map +1 -0
  194. package/dist/notifications/types.d.ts +19 -0
  195. package/dist/notifications/types.d.ts.map +1 -0
  196. package/dist/notifications/types.js +11 -0
  197. package/dist/notifications/types.js.map +1 -0
  198. package/package.json +101 -0
  199. package/src/db/migrations/0000_sharp_night_nurse.sql +52 -0
  200. package/src/db/migrations/0001_hesitant_vapor.sql +40 -0
  201. package/src/db/migrations/0002_numerous_lady_vermin.sql +235 -0
  202. package/src/db/migrations/0003_mighty_jean_grey.sql +37 -0
  203. package/src/db/migrations/0004_cynical_grandmaster.sql +2 -0
  204. package/src/db/migrations/0005_careful_switch.sql +8 -0
  205. package/src/db/migrations/0006_tense_blue_marvel.sql +16 -0
  206. package/src/db/migrations/0007_next_squadron_sinister.sql +1 -0
  207. package/src/db/migrations/0008_powerful_scream.sql +39 -0
  208. package/src/db/migrations/0009_sad_ink.sql +1 -0
  209. package/src/db/migrations/0010_shocking_omega_flight.sql +2 -0
  210. package/src/db/migrations/0011_ambiguous_otto_octavius.sql +151 -0
  211. package/src/db/migrations/0011_hnsw_vector_index.sql +17 -0
  212. package/src/db/migrations/0012_user_profiles.sql +30 -0
  213. package/src/db/migrations/0013_trending_seasonal_columns.sql +9 -0
  214. package/src/db/migrations/0014_add_product_type_to_agents.sql +6 -0
  215. package/src/db/migrations/0015_new_v1_tables.sql +149 -0
  216. package/src/db/migrations/0016_document_types.sql +30 -0
  217. package/src/db/migrations/0017_document_metadata.sql +2 -0
  218. package/src/db/migrations/0018_identity_graph.sql +200 -0
  219. package/src/db/migrations/0019_db_driven_prompts.sql +20 -0
  220. package/src/db/migrations/0020_pending_extractions.sql +35 -0
  221. package/src/db/migrations/0021_rag_documents.sql +39 -0
  222. package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +70 -0
  223. package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +17 -0
  224. package/src/db/migrations/0024_user_memories_hnsw.sql +23 -0
  225. package/src/db/migrations/0025_audit_trigger_errcode.sql +21 -0
  226. package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +17 -0
  227. package/src/db/migrations/0027_billing_settings_mnt_columns.sql +54 -0
  228. package/src/db/migrations/0028_trial_credits_normalized_email.sql +37 -0
  229. package/src/db/migrations/0029_usage_records_conversation_idx.sql +13 -0
  230. package/src/db/migrations/0030_pending_extractions_composite_idx.sql +11 -0
  231. package/src/db/migrations/0031_rag_documents_hnsw.sql +16 -0
  232. package/src/db/migrations/0032_rate_limit_unique_idx.sql +16 -0
  233. package/src/db/migrations/0033_pending_extractions_partial_idx.sql +18 -0
  234. package/src/db/migrations/0034_rate_limit_count.sql +9 -0
  235. package/src/db/migrations/0035_credit_reservations.sql +24 -0
  236. package/src/db/migrations/0036_executions_audit_jobs.sql +96 -0
  237. package/src/db/migrations/0037_rate_limit_minute_bucket.sql +46 -0
  238. package/src/db/migrations/0038_user_quotas_generic_usage.sql +25 -0
  239. package/src/db/migrations/0039_platform_admin_impersonation.sql +27 -0
  240. package/src/db/migrations/0040_ee_audit_chain.sql +23 -0
  241. package/src/db/migrations/README.md +57 -0
  242. package/src/db/migrations/meta/0000_snapshot.json +348 -0
  243. package/src/db/migrations/meta/0001_snapshot.json +631 -0
  244. package/src/db/migrations/meta/0002_snapshot.json +2269 -0
  245. package/src/db/migrations/meta/0003_snapshot.json +2488 -0
  246. package/src/db/migrations/meta/0004_snapshot.json +2500 -0
  247. package/src/db/migrations/meta/0005_snapshot.json +2548 -0
  248. package/src/db/migrations/meta/0006_snapshot.json +2650 -0
  249. package/src/db/migrations/meta/0007_snapshot.json +2657 -0
  250. package/src/db/migrations/meta/0008_snapshot.json +2719 -0
  251. package/src/db/migrations/meta/0009_snapshot.json +2726 -0
  252. package/src/db/migrations/meta/0010_snapshot.json +2739 -0
  253. package/src/db/migrations/meta/0011_snapshot.json +3886 -0
  254. package/src/db/migrations/meta/_journal.json +300 -0
@@ -0,0 +1,54 @@
1
+ -- Wave 4 — MNT-denominated billing + live settings row.
2
+ --
3
+ -- Adds the authoritative MNT columns that `packages/billing/src/quota.ts`
4
+ -- reads on every chat turn (chargedMnt, balanceMnt, credits*Mnt) and the
5
+ -- singleton `billing_settings` row that holds the current FX rate and
6
+ -- margin multiplier. The application reads these in-process with a 60s
7
+ -- TTL cache so prices can be tuned without a code push.
8
+ --
9
+ -- Safe to re-run: all statements use IF NOT EXISTS.
10
+ --
11
+ -- Apply with:
12
+ -- psql $DATABASE_URL -f packages/core/src/db/migrations/0027_billing_settings_mnt_columns.sql
13
+
14
+ -- billing_settings — singleton config row (id = 'default').
15
+ CREATE TABLE IF NOT EXISTS "billing_settings" (
16
+ "id" text PRIMARY KEY NOT NULL,
17
+ "usd_to_mnt_rate" integer NOT NULL DEFAULT 3450,
18
+ "margin_multiplier_bp" integer NOT NULL DEFAULT 400,
19
+ "updated_at" timestamp NOT NULL DEFAULT now()
20
+ );
21
+ --> statement-breakpoint
22
+
23
+ -- Seed the singleton row so getBillingSettings() has a hit on fresh installs.
24
+ INSERT INTO "billing_settings" ("id", "usd_to_mnt_rate", "margin_multiplier_bp")
25
+ VALUES ('default', 3450, 400)
26
+ ON CONFLICT ("id") DO NOTHING;
27
+ --> statement-breakpoint
28
+
29
+ -- credit_balances — MNT balance columns (legacy `balance` column left intact).
30
+ ALTER TABLE "credit_balances"
31
+ ADD COLUMN IF NOT EXISTS "balance_mnt" integer NOT NULL DEFAULT 0,
32
+ ADD COLUMN IF NOT EXISTS "total_purchased_mnt" integer NOT NULL DEFAULT 0,
33
+ ADD COLUMN IF NOT EXISTS "total_used_mnt" integer NOT NULL DEFAULT 0;
34
+ --> statement-breakpoint
35
+
36
+ -- usage_records — per-request MNT + trace fields.
37
+ ALTER TABLE "usage_records"
38
+ ADD COLUMN IF NOT EXISTS "margin_multiplier" real NOT NULL DEFAULT 0,
39
+ ADD COLUMN IF NOT EXISTS "fx_rate" real NOT NULL DEFAULT 0,
40
+ ADD COLUMN IF NOT EXISTS "charged_mnt" integer NOT NULL DEFAULT 0,
41
+ ADD COLUMN IF NOT EXISTS "request_id" text,
42
+ ADD COLUMN IF NOT EXISTS "conversation_id" text;
43
+ --> statement-breakpoint
44
+
45
+ -- monthly_usage — MNT aggregate for O(1) plan quota checks.
46
+ ALTER TABLE "monthly_usage"
47
+ ADD COLUMN IF NOT EXISTS "charged_mnt" integer NOT NULL DEFAULT 0;
48
+ --> statement-breakpoint
49
+
50
+ -- trial_credits — MNT-denominated grant replaces token counters.
51
+ ALTER TABLE "trial_credits"
52
+ ADD COLUMN IF NOT EXISTS "initial_credits_mnt" integer NOT NULL DEFAULT 5000,
53
+ ADD COLUMN IF NOT EXISTS "credits_used_mnt" integer NOT NULL DEFAULT 0,
54
+ ADD COLUMN IF NOT EXISTS "credits_remaining_mnt" integer NOT NULL DEFAULT 5000;
@@ -0,0 +1,37 @@
1
+ -- Indexed per-email trial abuse check.
2
+ --
3
+ -- checkTrialAbuse() previously did `SELECT created_by_email FROM
4
+ -- trial_credits` (no WHERE, no LIMIT) and filtered in JS. That's a
5
+ -- full-table scan on every signup. Adding a normalized_email column
6
+ -- (populated at provisioning time) plus a btree index lets the same
7
+ -- check become `SELECT count(*) WHERE normalized_email = ?` — O(log n)
8
+ -- with index-only scan.
9
+ --
10
+ -- Apply with:
11
+ -- psql $DATABASE_URL -f packages/core/src/db/migrations/0028_trial_credits_normalized_email.sql
12
+
13
+ ALTER TABLE "trial_credits"
14
+ ADD COLUMN IF NOT EXISTS "normalized_email" text;
15
+ --> statement-breakpoint
16
+
17
+ -- Backfill existing rows so the abuse check behaves consistently for
18
+ -- trials provisioned before this migration. Mirrors the JS rule in
19
+ -- normalizeEmailForAbuseCheck: lowercase, drop +aliases, collapse dots
20
+ -- on gmail/googlemail, rewrite googlemail.com → gmail.com.
21
+ UPDATE "trial_credits"
22
+ SET "normalized_email" = (
23
+ CASE
24
+ WHEN split_part(lower(trim("created_by_email")), '@', 2) IN ('gmail.com', 'googlemail.com')
25
+ THEN replace(split_part(split_part(lower(trim("created_by_email")), '@', 1), '+', 1), '.', '')
26
+ || '@gmail.com'
27
+ ELSE split_part(split_part(lower(trim("created_by_email")), '@', 1), '+', 1)
28
+ || '@'
29
+ || split_part(lower(trim("created_by_email")), '@', 2)
30
+ END
31
+ )
32
+ WHERE "created_by_email" IS NOT NULL
33
+ AND "normalized_email" IS NULL;
34
+ --> statement-breakpoint
35
+
36
+ CREATE INDEX IF NOT EXISTS "trial_credits_normalized_email_idx"
37
+ ON "trial_credits" ("normalized_email");
@@ -0,0 +1,13 @@
1
+ -- Cover the conversation deep-link path on the usage dashboard.
2
+ --
3
+ -- usage_table.tsx renders a per-row link to /chat/<conversationId>;
4
+ -- once traffic grows, any reverse lookup (e.g. "all usage for this
5
+ -- conversation") has to seq-scan on the nullable conversation_id
6
+ -- column. Adding the index now while the table is still small is
7
+ -- cheap and future-proofs the lookup path.
8
+ --
9
+ -- Apply with:
10
+ -- psql $DATABASE_URL -f packages/core/src/db/migrations/0029_usage_records_conversation_idx.sql
11
+
12
+ CREATE INDEX IF NOT EXISTS "usage_records_conversation_id_idx"
13
+ ON "usage_records" ("conversation_id");
@@ -0,0 +1,11 @@
1
+ -- DB-02: Composite index for efficient workspace-scoped extraction polling.
2
+ --
3
+ -- The worker queries "status = 'pending'" filtered by workspaceId, but the
4
+ -- existing index is on status alone. A scan across all workspaces is
5
+ -- expensive as the queue grows. This composite index allows index-only
6
+ -- scan for workspace-scoped pending-extraction queries.
7
+ --
8
+ -- Apply with: psql $DATABASE_URL -f packages/core/src/db/migrations/0030_pending_extractions_composite_idx.sql
9
+
10
+ CREATE INDEX IF NOT EXISTS "pending_extractions_status_workspace_idx"
11
+ ON "pending_extractions" ("status", "workspace_id");
@@ -0,0 +1,16 @@
1
+ -- DB-04: Migrate rag_documents vector index from IVFFlat to HNSW.
2
+ --
3
+ -- IVFFlat with lists=100 degrades at < 100k docs (probe count dominates,
4
+ -- approximation quality drops). HNSW has no tuning cliff — recall is good
5
+ -- from a handful of rows through tens of millions.
6
+ --
7
+ -- Template: 0011_hnsw_vector_index.sql + 0024_user_memories_hnsw.sql
8
+ -- m / ef_construction follow pgvector docs defaults for text embeddings.
9
+ --
10
+ -- Apply with: psql $DATABASE_URL -f packages/core/src/db/migrations/0031_rag_documents_hnsw.sql
11
+
12
+ DROP INDEX IF EXISTS "rag_documents_embedding_idx";
13
+ --> statement-breakpoint
14
+ CREATE INDEX "rag_documents_embedding_hnsw_idx"
15
+ ON "rag_documents" USING hnsw ("embedding" vector_cosine_ops)
16
+ WITH (m = 16, ef_construction = 64);
@@ -0,0 +1,16 @@
1
+ -- DB-06: Unique constraint for rate limit deduplication.
2
+ --
3
+ -- Enables INSERT ... ON CONFLICT DO NOTHING as the authoritative rate limit
4
+ -- check instead of pre-count + insert (which allows 2× concurrent requests through).
5
+ -- The composite unique index is on (workspaceId, endpoint, minute_bucket).
6
+ -- minute_bucket floors requestedAt to the minute, so entries within the same
7
+ -- minute window conflict and only the first is inserted.
8
+ --
9
+ -- Apply with: psql $DATABASE_URL -f packages/core/src/db/migrations/0032_rate_limit_unique_idx.sql
10
+
11
+ CREATE UNIQUE INDEX IF NOT EXISTS "rate_limit_entries_unique_bucket_idx"
12
+ ON "rate_limit_entries" (
13
+ "workspace_id",
14
+ "endpoint",
15
+ date_trunc('minute', "requested_at")
16
+ );
@@ -0,0 +1,18 @@
1
+ -- DB-01: Partial index for workspace-scoped pending extraction polling.
2
+ --
3
+ -- Workers claim rows by: WHERE status = 'pending' AND workspace_id = ?
4
+ -- The existing composite index (status, workspace_id) forces a scan of
5
+ -- ALL pending rows across all workspaces before filtering by workspace.
6
+ -- This partial index narrows the scan to just pending rows for the target
7
+ -- workspace — an index-only scan at O(log n) per workspace instead of
8
+ -- O(pending_rows_total).
9
+ --
10
+ -- Applies to: claimNextExtraction() and listPendingExtractions() in
11
+ -- packages/agents/src/extraction/queue.ts
12
+ --
13
+ -- Run with: psql $DATABASE_URL -f packages/core/src/db/migrations/0033_pending_extractions_partial_idx.sql
14
+ -- Or via: cd packages/core && pnpm drizzle-kit push
15
+
16
+ CREATE INDEX IF NOT EXISTS pending_extractions_workspace_pending_idx
17
+ ON pending_extractions (workspace_id)
18
+ WHERE status = 'pending';
@@ -0,0 +1,9 @@
1
+ -- B-05b: Rate limiter admits only ONE request per minute bucket.
2
+ --
3
+ -- The B-05 fix replaced check-then-insert with a unique constraint on
4
+ -- (workspace_id, endpoint, minute_bucket) + ON CONFLICT DO NOTHING, but
5
+ -- that admits exactly one request per bucket — the per-plan limit
6
+ -- (10/60/300 rpm) was never consulted. Add a counter column so the
7
+ -- upsert becomes DO UPDATE SET count = count + 1 RETURNING count, and
8
+ -- admission is count <= limit.
9
+ ALTER TABLE "rate_limit_entries" ADD COLUMN IF NOT EXISTS "count" integer DEFAULT 1 NOT NULL;
@@ -0,0 +1,24 @@
1
+ -- B-06: Close the checkQuota → recordTokenUsage TOCTOU window.
2
+ --
3
+ -- checkQuota was a pure read; the debit happened minutes later in
4
+ -- recordTokenUsage, so N concurrent requests could all pass admission
5
+ -- against the same balance. Admission now inserts a reservation for the
6
+ -- worst-case MNT estimate in the same transaction that sums the other
7
+ -- active reservations. recordTokenUsage settles it; abandoned rows
8
+ -- expire via expires_at (TTL >= chat route maxDuration) and are cleaned
9
+ -- by the cleanup cron.
10
+ CREATE TABLE IF NOT EXISTS "credit_reservations" (
11
+ "id" text PRIMARY KEY NOT NULL,
12
+ "workspace_id" text NOT NULL,
13
+ "request_id" text NOT NULL,
14
+ "estimated_mnt" integer NOT NULL,
15
+ "status" text DEFAULT 'active' NOT NULL,
16
+ "created_at" timestamp DEFAULT now() NOT NULL,
17
+ "expires_at" timestamp NOT NULL,
18
+ "settled_at" timestamp,
19
+ CONSTRAINT "credit_reservations_request_id_unique" UNIQUE("request_id")
20
+ );
21
+ --> statement-breakpoint
22
+ ALTER TABLE "credit_reservations" ADD CONSTRAINT "credit_reservations_workspace_id_organization_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;
23
+ --> statement-breakpoint
24
+ CREATE INDEX IF NOT EXISTS "credit_reservations_active_idx" ON "credit_reservations" USING btree ("workspace_id","status","expires_at");
@@ -0,0 +1,96 @@
1
+ -- Phase 2: the SaaS execution boundary (ADR-0003, ADR-0004).
2
+ --
3
+ -- Three tables, three owning packages:
4
+ -- executions → @intelligo-dev/executions — one row per AI run: actor,
5
+ -- workspace, capability, entitlement outcome, status,
6
+ -- usage, cost. Records nothing about agents/tools/
7
+ -- messages, which stay native to the AI framework.
8
+ -- audit_events → @intelligo-dev/audit — append-only who-did-what.
9
+ -- jobs → @intelligo-dev/jobs — Postgres-backed queue (claimed
10
+ -- with FOR UPDATE SKIP LOCKED).
11
+ --
12
+ -- usage_records gains execution_id rather than being duplicated into a
13
+ -- new usage_events table: it already IS the per-request usage/cost
14
+ -- detail. The rename happens with the Phase 4 package split.
15
+
16
+ CREATE TABLE IF NOT EXISTS "executions" (
17
+ "id" text PRIMARY KEY NOT NULL,
18
+ "workspace_id" text NOT NULL,
19
+ "user_id" text,
20
+ "capability" text NOT NULL,
21
+ "request_id" text NOT NULL,
22
+ "status" text DEFAULT 'running' NOT NULL,
23
+ "model" text,
24
+ "input_tokens" integer,
25
+ "output_tokens" integer,
26
+ "total_tokens" integer,
27
+ "charged_mnt" integer,
28
+ "reserved_mnt" integer,
29
+ "refusal_reason" text,
30
+ "error_message" text,
31
+ "started_at" timestamp DEFAULT now() NOT NULL,
32
+ "finished_at" timestamp,
33
+ "duration_ms" integer,
34
+ "metadata" jsonb,
35
+ CONSTRAINT "executions_request_id_unique" UNIQUE("request_id")
36
+ );
37
+ --> statement-breakpoint
38
+ CREATE TABLE IF NOT EXISTS "audit_events" (
39
+ "id" text PRIMARY KEY NOT NULL,
40
+ "workspace_id" text,
41
+ "actor_id" text,
42
+ "actor_kind" text DEFAULT 'user' NOT NULL,
43
+ "action" text NOT NULL,
44
+ "resource_kind" text NOT NULL,
45
+ "resource_id" text,
46
+ "outcome" text DEFAULT 'ok' NOT NULL,
47
+ "metadata" jsonb,
48
+ "created_at" timestamp DEFAULT now() NOT NULL
49
+ );
50
+ --> statement-breakpoint
51
+ CREATE TABLE IF NOT EXISTS "jobs" (
52
+ "id" text PRIMARY KEY NOT NULL,
53
+ "workspace_id" text,
54
+ "kind" text NOT NULL,
55
+ "payload" jsonb,
56
+ "status" text DEFAULT 'pending' NOT NULL,
57
+ "attempts" integer DEFAULT 0 NOT NULL,
58
+ "max_attempts" integer DEFAULT 3 NOT NULL,
59
+ "run_at" timestamp DEFAULT now() NOT NULL,
60
+ "started_at" timestamp,
61
+ "finished_at" timestamp,
62
+ "last_error" text,
63
+ "created_at" timestamp DEFAULT now() NOT NULL
64
+ );
65
+ --> statement-breakpoint
66
+ ALTER TABLE "usage_records" ADD COLUMN IF NOT EXISTS "execution_id" text;
67
+ --> statement-breakpoint
68
+ ALTER TABLE "executions" ADD CONSTRAINT "executions_workspace_id_organization_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;
69
+ --> statement-breakpoint
70
+ ALTER TABLE "executions" ADD CONSTRAINT "executions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
71
+ --> statement-breakpoint
72
+ ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_workspace_id_organization_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;
73
+ --> statement-breakpoint
74
+ ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_actor_id_users_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
75
+ --> statement-breakpoint
76
+ ALTER TABLE "jobs" ADD CONSTRAINT "jobs_workspace_id_organization_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;
77
+ --> statement-breakpoint
78
+ CREATE INDEX IF NOT EXISTS "executions_workspace_started_idx" ON "executions" USING btree ("workspace_id","started_at");
79
+ --> statement-breakpoint
80
+ CREATE INDEX IF NOT EXISTS "executions_status_idx" ON "executions" USING btree ("status","started_at");
81
+ --> statement-breakpoint
82
+ CREATE INDEX IF NOT EXISTS "executions_capability_idx" ON "executions" USING btree ("capability","started_at");
83
+ --> statement-breakpoint
84
+ CREATE INDEX IF NOT EXISTS "audit_events_workspace_created_idx" ON "audit_events" USING btree ("workspace_id","created_at");
85
+ --> statement-breakpoint
86
+ CREATE INDEX IF NOT EXISTS "audit_events_action_idx" ON "audit_events" USING btree ("action","created_at");
87
+ --> statement-breakpoint
88
+ CREATE INDEX IF NOT EXISTS "audit_events_resource_idx" ON "audit_events" USING btree ("resource_kind","resource_id");
89
+ --> statement-breakpoint
90
+ CREATE INDEX IF NOT EXISTS "jobs_claim_idx" ON "jobs" USING btree ("status","run_at");
91
+ --> statement-breakpoint
92
+ CREATE INDEX IF NOT EXISTS "jobs_kind_idx" ON "jobs" USING btree ("kind","status");
93
+ --> statement-breakpoint
94
+ CREATE INDEX IF NOT EXISTS "jobs_workspace_idx" ON "jobs" USING btree ("workspace_id");
95
+ --> statement-breakpoint
96
+ CREATE INDEX IF NOT EXISTS "usage_records_execution_id_idx" ON "usage_records" USING btree ("execution_id");
@@ -0,0 +1,46 @@
1
+ -- Repair the rate-limit schema so a fresh database matches the code.
2
+ --
3
+ -- 0032 documented a unique constraint on (workspace_id, endpoint,
4
+ -- minute_bucket) but created an EXPRESSION index on
5
+ -- date_trunc('minute', requested_at) instead, and no migration ever
6
+ -- added the minute_bucket column. checkRateLimit's
7
+ -- `ON CONFLICT (workspace_id, endpoint, minute_bucket)` can only
8
+ -- resolve against a constraint on those literal columns, so replaying
9
+ -- the committed history against an empty database produced a
10
+ -- rate_limit_entries table that throws
11
+ -- `column "minute_bucket" does not exist` on every chat request.
12
+ -- Production only worked because the live database had been synced
13
+ -- with `drizzle-kit push` straight from schema.ts, bypassing these
14
+ -- files entirely.
15
+
16
+ ALTER TABLE "rate_limit_entries"
17
+ ADD COLUMN IF NOT EXISTS "minute_bucket" timestamp;
18
+ --> statement-breakpoint
19
+
20
+ -- Backfill so the NOT NULL below can be applied to existing rows.
21
+ UPDATE "rate_limit_entries"
22
+ SET "minute_bucket" = date_trunc('minute', "requested_at")
23
+ WHERE "minute_bucket" IS NULL;
24
+ --> statement-breakpoint
25
+
26
+ ALTER TABLE "rate_limit_entries"
27
+ ALTER COLUMN "minute_bucket" SET NOT NULL;
28
+ --> statement-breakpoint
29
+
30
+ -- The expression index carries the name the constraint needs.
31
+ DROP INDEX IF EXISTS "rate_limit_entries_unique_bucket_idx";
32
+ --> statement-breakpoint
33
+
34
+ -- Collapse any duplicate buckets the expression index allowed through
35
+ -- before the real constraint can be created.
36
+ DELETE FROM "rate_limit_entries" a
37
+ USING "rate_limit_entries" b
38
+ WHERE a.ctid < b.ctid
39
+ AND a."workspace_id" = b."workspace_id"
40
+ AND a."endpoint" = b."endpoint"
41
+ AND a."minute_bucket" = b."minute_bucket";
42
+ --> statement-breakpoint
43
+
44
+ ALTER TABLE "rate_limit_entries"
45
+ ADD CONSTRAINT "rate_limit_entries_unique_bucket_idx"
46
+ UNIQUE ("workspace_id", "endpoint", "minute_bucket");
@@ -0,0 +1,25 @@
1
+ -- Finish the user_quotas cutover to a generic usage map (B6).
2
+ --
3
+ -- The table carried three career-specific counters — chat_messages_used,
4
+ -- assessments_used, reports_used — alongside the generic `usage` JSONB
5
+ -- added in migration 0022. Since then recordFeatureUsage dual-wrote both
6
+ -- while reads still preferred the columns, so a public package's schema
7
+ -- named a vertical's actions and no vertical could add a fourth counter
8
+ -- without a migration to Intelligo (ADR-0004, ADR-0006).
9
+ --
10
+ -- Backfill first: rows written since the dual-write started already have
11
+ -- the keys in `usage`, and those values win. Rows that predate it have a
12
+ -- non-zero column and no key, so the built object supplies them. `||` is
13
+ -- right-biased, hence usage on the right.
14
+
15
+ UPDATE user_quotas
16
+ SET usage = jsonb_build_object(
17
+ 'chat', COALESCE(chat_messages_used, 0),
18
+ 'assessment', COALESCE(assessments_used, 0),
19
+ 'report', COALESCE(reports_used, 0)
20
+ ) || COALESCE(usage, '{}'::jsonb);
21
+
22
+ ALTER TABLE user_quotas
23
+ DROP COLUMN IF EXISTS chat_messages_used,
24
+ DROP COLUMN IF EXISTS assessments_used,
25
+ DROP COLUMN IF EXISTS reports_used;
@@ -0,0 +1,27 @@
1
+ -- Platform role and impersonation support.
2
+ --
3
+ -- `users.role` makes platform admin a property of the user rather than
4
+ -- of the environment. PLATFORM_ADMIN_EMAILS stays as the bootstrap —
5
+ -- requirePlatformAdmin promotes an allowlisted user into this column on
6
+ -- first use — so a fresh deployment still has a way in, but the runtime
7
+ -- authority is a row, which Better-Auth's admin plugin can read and
8
+ -- which survives someone editing the env.
9
+ --
10
+ -- The ban columns are unused by the product; the admin plugin's schema
11
+ -- expects them and Better-Auth errors on a missing column rather than
12
+ -- degrading.
13
+ --
14
+ -- `sessions.impersonated_by` is what makes an impersonated session
15
+ -- recognisable as one after the fact, at the session itself and not
16
+ -- only in the audit trail.
17
+
18
+ ALTER TABLE users
19
+ ADD COLUMN IF NOT EXISTS role text,
20
+ ADD COLUMN IF NOT EXISTS banned boolean NOT NULL DEFAULT false,
21
+ ADD COLUMN IF NOT EXISTS ban_reason text,
22
+ ADD COLUMN IF NOT EXISTS ban_expires timestamp;
23
+
24
+ ALTER TABLE sessions
25
+ ADD COLUMN IF NOT EXISTS impersonated_by text;
26
+
27
+ CREATE INDEX IF NOT EXISTS users_role_idx ON users (role) WHERE role IS NOT NULL;
@@ -0,0 +1,23 @@
1
+ -- Tamper-evidence chain over the audit trail.
2
+ --
3
+ -- Owned by @intelligo-dev/ee-governance, a package outside the public
4
+ -- allowlist. It appears in this shared history because Core owns the
5
+ -- migration chain for the whole database (ADR-0004) — but no public
6
+ -- package declares the table, imports the module, or knows the feature
7
+ -- exists. Removing the module removes the capability and nothing else.
8
+ --
9
+ -- No foreign key to audit_events on purpose: audit is append-only and
10
+ -- owned elsewhere, and a cascade from there would erase the evidence
11
+ -- that something was erased, which is the one thing this table is for.
12
+
13
+ CREATE TABLE IF NOT EXISTS ee_audit_chain (
14
+ event_id text PRIMARY KEY,
15
+ workspace_id text,
16
+ sequence integer NOT NULL,
17
+ previous_hash text NOT NULL,
18
+ hash text NOT NULL,
19
+ created_at timestamp NOT NULL DEFAULT now()
20
+ );
21
+
22
+ CREATE INDEX IF NOT EXISTS ee_audit_chain_workspace_seq_idx
23
+ ON ee_audit_chain (workspace_id, sequence);
@@ -0,0 +1,57 @@
1
+ # Migrations
2
+
3
+ `drizzle-kit migrate` applies the `.sql` files listed in
4
+ `meta/_journal.json`, in journal order. Both halves have to agree, and
5
+ for most of this repository's life they did not: the journal stopped at
6
+ entry 11 while the directory grew to 39 files, so `migrate` silently
7
+ applied a quarter of the chain and every environment was really
8
+ provisioned by `drizzle-kit push` from `schema.ts`.
9
+
10
+ That is how `0032` shipped documenting a unique constraint on a
11
+ `minute_bucket` column no migration ever created — the rate limiter's
12
+ `ON CONFLICT` could not have resolved on a database built from these
13
+ files. `0037` repairs it, and `intelligo doctor` now fails CI if the
14
+ journal and the directory drift apart again.
15
+
16
+ ## Adding a migration
17
+
18
+ Prefer `pnpm --filter @intelligo-dev/core db:generate`, which writes both
19
+ the `.sql` file and its journal entry. A hand-written file needs its
20
+ tag added to `meta/_journal.json` by hand — `doctor` will tell you if
21
+ you forget.
22
+
23
+ Note that `db:generate` diffs against `meta/NNNN_snapshot.json`, and
24
+ snapshots exist only up to `0011`. Until the snapshots are rebuilt,
25
+ generate produces a full-schema diff rather than an incremental one;
26
+ hand-write the file and add the journal entry instead.
27
+
28
+ ## Baselining an existing database
29
+
30
+ Because the journal previously listed only 12 of the 39 files, a
31
+ database provisioned with `push` has the schema but no record of the
32
+ migrations. Running `migrate` against it now would try to apply all 39.
33
+ Most are `IF NOT EXISTS`-guarded, but not all.
34
+
35
+ Mark them as already applied before the first `migrate` on such a
36
+ database:
37
+
38
+ ```sql
39
+ -- One row per journal entry, in journal order. hash is the sha256 of
40
+ -- the .sql file contents; drizzle compares it on subsequent runs.
41
+ CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
42
+ id SERIAL PRIMARY KEY,
43
+ hash text NOT NULL,
44
+ created_at bigint
45
+ );
46
+ ```
47
+
48
+ Generate the rows with the file hashes and the `when` values from the
49
+ journal, then verify with `intelligo doctor` and a `migrate` run that
50
+ reports nothing to apply. Do this on a restored copy first.
51
+
52
+ ## Verifying the chain
53
+
54
+ CI replays every file against an empty database on each run (see the
55
+ `e2e` job) and then asserts the columns the running code depends on.
56
+ That check is what proves these files are a provisioning mechanism
57
+ rather than documentation.