@minion-stack/db 0.3.1 → 0.6.0

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 (135) hide show
  1. package/dist/crypto.d.ts +19 -0
  2. package/dist/crypto.d.ts.map +1 -0
  3. package/dist/crypto.js +65 -0
  4. package/dist/crypto.js.map +1 -0
  5. package/dist/crypto.test.d.ts +2 -0
  6. package/dist/crypto.test.d.ts.map +1 -0
  7. package/dist/crypto.test.js +23 -0
  8. package/dist/crypto.test.js.map +1 -0
  9. package/dist/pg/crypto.d.ts +1 -7
  10. package/dist/pg/crypto.d.ts.map +1 -1
  11. package/dist/pg/crypto.js +4 -41
  12. package/dist/pg/crypto.js.map +1 -1
  13. package/dist/pg/schema/agent-groups.d.ts +169 -0
  14. package/dist/pg/schema/agent-groups.d.ts.map +1 -0
  15. package/dist/pg/schema/agent-groups.js +21 -0
  16. package/dist/pg/schema/agent-groups.js.map +1 -0
  17. package/dist/pg/schema/builder.d.ts +1432 -0
  18. package/dist/pg/schema/builder.d.ts.map +1 -0
  19. package/dist/pg/schema/builder.js +160 -0
  20. package/dist/pg/schema/builder.js.map +1 -0
  21. package/dist/pg/schema/channels.d.ts +441 -0
  22. package/dist/pg/schema/channels.d.ts.map +1 -0
  23. package/dist/pg/schema/channels.js +62 -0
  24. package/dist/pg/schema/channels.js.map +1 -0
  25. package/dist/pg/schema/chat-messages.d.ts +184 -0
  26. package/dist/pg/schema/chat-messages.d.ts.map +1 -0
  27. package/dist/pg/schema/chat-messages.js +26 -0
  28. package/dist/pg/schema/chat-messages.js.map +1 -0
  29. package/dist/pg/schema/device-identities.d.ts +111 -0
  30. package/dist/pg/schema/device-identities.d.ts.map +1 -0
  31. package/dist/pg/schema/device-identities.js +11 -0
  32. package/dist/pg/schema/device-identities.js.map +1 -0
  33. package/dist/pg/schema/files.d.ts +162 -0
  34. package/dist/pg/schema/files.d.ts.map +1 -0
  35. package/dist/pg/schema/files.js +15 -0
  36. package/dist/pg/schema/files.js.map +1 -0
  37. package/dist/pg/schema/index.d.ts +19 -0
  38. package/dist/pg/schema/index.d.ts.map +1 -1
  39. package/dist/pg/schema/index.js +20 -0
  40. package/dist/pg/schema/index.js.map +1 -1
  41. package/dist/pg/schema/marketplace.d.ts +459 -0
  42. package/dist/pg/schema/marketplace.d.ts.map +1 -0
  43. package/dist/pg/schema/marketplace.js +42 -0
  44. package/dist/pg/schema/marketplace.js.map +1 -0
  45. package/dist/pg/schema/messages.d.ts +391 -0
  46. package/dist/pg/schema/messages.d.ts.map +1 -0
  47. package/dist/pg/schema/messages.js +44 -0
  48. package/dist/pg/schema/messages.js.map +1 -0
  49. package/dist/pg/schema/missions.d.ts +361 -0
  50. package/dist/pg/schema/missions.d.ts.map +1 -0
  51. package/dist/pg/schema/missions.js +48 -0
  52. package/dist/pg/schema/missions.js.map +1 -0
  53. package/dist/pg/schema/personal-agents.d.ts +285 -0
  54. package/dist/pg/schema/personal-agents.d.ts.map +1 -0
  55. package/dist/pg/schema/personal-agents.js +40 -0
  56. package/dist/pg/schema/personal-agents.js.map +1 -0
  57. package/dist/pg/schema/profiles.d.ts +17 -0
  58. package/dist/pg/schema/profiles.d.ts.map +1 -1
  59. package/dist/pg/schema/profiles.js +2 -0
  60. package/dist/pg/schema/profiles.js.map +1 -1
  61. package/dist/pg/schema/server-ops.d.ts +836 -0
  62. package/dist/pg/schema/server-ops.d.ts.map +1 -0
  63. package/dist/pg/schema/server-ops.js +88 -0
  64. package/dist/pg/schema/server-ops.js.map +1 -0
  65. package/dist/pg/schema/sessions.d.ts +395 -0
  66. package/dist/pg/schema/sessions.d.ts.map +1 -0
  67. package/dist/pg/schema/sessions.js +50 -0
  68. package/dist/pg/schema/sessions.js.map +1 -0
  69. package/dist/pg/schema/settings.d.ts +111 -0
  70. package/dist/pg/schema/settings.d.ts.map +1 -0
  71. package/dist/pg/schema/settings.js +17 -0
  72. package/dist/pg/schema/settings.js.map +1 -0
  73. package/dist/pg/schema/skills.d.ts +395 -0
  74. package/dist/pg/schema/skills.d.ts.map +1 -0
  75. package/dist/pg/schema/skills.js +45 -0
  76. package/dist/pg/schema/skills.js.map +1 -0
  77. package/dist/pg/schema/user-agents.d.ts +80 -0
  78. package/dist/pg/schema/user-agents.d.ts.map +1 -0
  79. package/dist/pg/schema/user-agents.js +21 -0
  80. package/dist/pg/schema/user-agents.js.map +1 -0
  81. package/dist/pg/schema/user-identities.d.ts +1 -1
  82. package/dist/pg/schema/user-preferences.d.ts +97 -0
  83. package/dist/pg/schema/user-preferences.d.ts.map +1 -0
  84. package/dist/pg/schema/user-preferences.js +19 -0
  85. package/dist/pg/schema/user-preferences.js.map +1 -0
  86. package/dist/pg/schema/workshop-saves.d.ts +145 -0
  87. package/dist/pg/schema/workshop-saves.d.ts.map +1 -0
  88. package/dist/pg/schema/workshop-saves.js +13 -0
  89. package/dist/pg/schema/workshop-saves.js.map +1 -0
  90. package/dist/pg/schema/workspace-membership.d.ts +83 -0
  91. package/dist/pg/schema/workspace-membership.d.ts.map +1 -0
  92. package/dist/pg/schema/workspace-membership.js +19 -0
  93. package/dist/pg/schema/workspace-membership.js.map +1 -0
  94. package/dist/schema/flows.d.ts +36 -0
  95. package/dist/schema/flows.d.ts.map +1 -1
  96. package/dist/schema/flows.js +2 -0
  97. package/dist/schema/flows.js.map +1 -1
  98. package/dist/schema/index.d.ts +2 -0
  99. package/dist/schema/index.d.ts.map +1 -1
  100. package/dist/schema/index.js +1 -0
  101. package/dist/schema/index.js.map +1 -1
  102. package/dist/schema/join-requests.d.ts +188 -0
  103. package/dist/schema/join-requests.d.ts.map +1 -0
  104. package/dist/schema/join-requests.js +35 -0
  105. package/dist/schema/join-requests.js.map +1 -0
  106. package/dist/schema/personal-agents.d.ts +1 -1
  107. package/dist/schema/reliability-events.d.ts +1 -1
  108. package/dist/schema/skill-execution-stats.d.ts +1 -1
  109. package/package.json +15 -12
  110. package/src/crypto.test.ts +33 -0
  111. package/src/crypto.ts +73 -0
  112. package/src/pg/crypto.ts +4 -44
  113. package/src/pg/schema/agent-groups.ts +30 -0
  114. package/src/pg/schema/builder.ts +205 -0
  115. package/src/pg/schema/channels.ts +77 -0
  116. package/src/pg/schema/chat-messages.ts +30 -0
  117. package/src/pg/schema/device-identities.ts +11 -0
  118. package/src/pg/schema/files.ts +19 -0
  119. package/src/pg/schema/index.ts +36 -0
  120. package/src/pg/schema/marketplace.ts +47 -0
  121. package/src/pg/schema/messages.ts +48 -0
  122. package/src/pg/schema/missions.ts +58 -0
  123. package/src/pg/schema/personal-agents.ts +44 -0
  124. package/src/pg/schema/profiles.ts +2 -0
  125. package/src/pg/schema/server-ops.ts +126 -0
  126. package/src/pg/schema/sessions.ts +60 -0
  127. package/src/pg/schema/settings.ts +21 -0
  128. package/src/pg/schema/skills.ts +65 -0
  129. package/src/pg/schema/user-agents.ts +25 -0
  130. package/src/pg/schema/user-preferences.ts +23 -0
  131. package/src/pg/schema/workshop-saves.ts +13 -0
  132. package/src/pg/schema/workspace-membership.ts +26 -0
  133. package/src/schema/flows.ts +2 -0
  134. package/src/schema/index.ts +2 -0
  135. package/src/schema/join-requests.ts +42 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * join_requests — users requesting to join an organization.
3
+ *
4
+ * When a user not-yet-in-an-org wants access, they submit a join request.
5
+ * Org admins review (approve/deny). On approval the user is added as a member.
6
+ *
7
+ * Used by:
8
+ * - /join page (submission)
9
+ * - /api/join-requests/* (listing, counting, review)
10
+ * - notifications panel
11
+ */
12
+ export declare const joinRequests: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
13
+ name: "join_requests";
14
+ schema: undefined;
15
+ columns: {
16
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
17
+ name: "id";
18
+ tableName: "join_requests";
19
+ dataType: "string";
20
+ columnType: "SQLiteText";
21
+ data: string;
22
+ driverParam: string;
23
+ notNull: true;
24
+ hasDefault: false;
25
+ isPrimaryKey: true;
26
+ isAutoincrement: false;
27
+ hasRuntimeDefault: false;
28
+ enumValues: [string, ...string[]];
29
+ baseColumn: never;
30
+ identity: undefined;
31
+ generated: undefined;
32
+ }, {}, {
33
+ length: number | undefined;
34
+ }>;
35
+ userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
36
+ name: "user_id";
37
+ tableName: "join_requests";
38
+ dataType: "string";
39
+ columnType: "SQLiteText";
40
+ data: string;
41
+ driverParam: string;
42
+ notNull: true;
43
+ hasDefault: false;
44
+ isPrimaryKey: false;
45
+ isAutoincrement: false;
46
+ hasRuntimeDefault: false;
47
+ enumValues: [string, ...string[]];
48
+ baseColumn: never;
49
+ identity: undefined;
50
+ generated: undefined;
51
+ }, {}, {
52
+ length: number | undefined;
53
+ }>;
54
+ orgId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
55
+ name: "org_id";
56
+ tableName: "join_requests";
57
+ dataType: "string";
58
+ columnType: "SQLiteText";
59
+ data: string;
60
+ driverParam: string;
61
+ notNull: true;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: [string, ...string[]];
67
+ baseColumn: never;
68
+ identity: undefined;
69
+ generated: undefined;
70
+ }, {}, {
71
+ length: number | undefined;
72
+ }>;
73
+ email: import("drizzle-orm/sqlite-core").SQLiteColumn<{
74
+ name: "email";
75
+ tableName: "join_requests";
76
+ dataType: "string";
77
+ columnType: "SQLiteText";
78
+ data: string;
79
+ driverParam: string;
80
+ notNull: true;
81
+ hasDefault: false;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {
90
+ length: number | undefined;
91
+ }>;
92
+ message: import("drizzle-orm/sqlite-core").SQLiteColumn<{
93
+ name: "message";
94
+ tableName: "join_requests";
95
+ dataType: "string";
96
+ columnType: "SQLiteText";
97
+ data: string;
98
+ driverParam: string;
99
+ notNull: false;
100
+ hasDefault: false;
101
+ isPrimaryKey: false;
102
+ isAutoincrement: false;
103
+ hasRuntimeDefault: false;
104
+ enumValues: [string, ...string[]];
105
+ baseColumn: never;
106
+ identity: undefined;
107
+ generated: undefined;
108
+ }, {}, {
109
+ length: number | undefined;
110
+ }>;
111
+ status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
112
+ name: "status";
113
+ tableName: "join_requests";
114
+ dataType: "string";
115
+ columnType: "SQLiteText";
116
+ data: "pending" | "approved" | "denied";
117
+ driverParam: string;
118
+ notNull: true;
119
+ hasDefault: true;
120
+ isPrimaryKey: false;
121
+ isAutoincrement: false;
122
+ hasRuntimeDefault: false;
123
+ enumValues: ["pending", "approved", "denied"];
124
+ baseColumn: never;
125
+ identity: undefined;
126
+ generated: undefined;
127
+ }, {}, {
128
+ length: number | undefined;
129
+ }>;
130
+ reviewedBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
131
+ name: "reviewed_by";
132
+ tableName: "join_requests";
133
+ dataType: "string";
134
+ columnType: "SQLiteText";
135
+ data: string;
136
+ driverParam: string;
137
+ notNull: false;
138
+ hasDefault: false;
139
+ isPrimaryKey: false;
140
+ isAutoincrement: false;
141
+ hasRuntimeDefault: false;
142
+ enumValues: [string, ...string[]];
143
+ baseColumn: never;
144
+ identity: undefined;
145
+ generated: undefined;
146
+ }, {}, {
147
+ length: number | undefined;
148
+ }>;
149
+ reviewedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
150
+ name: "reviewed_at";
151
+ tableName: "join_requests";
152
+ dataType: "number";
153
+ columnType: "SQLiteInteger";
154
+ data: number;
155
+ driverParam: number;
156
+ notNull: false;
157
+ hasDefault: false;
158
+ isPrimaryKey: false;
159
+ isAutoincrement: false;
160
+ hasRuntimeDefault: false;
161
+ enumValues: undefined;
162
+ baseColumn: never;
163
+ identity: undefined;
164
+ generated: undefined;
165
+ }, {}, {}>;
166
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
167
+ name: "created_at";
168
+ tableName: "join_requests";
169
+ dataType: "number";
170
+ columnType: "SQLiteInteger";
171
+ data: number;
172
+ driverParam: number;
173
+ notNull: true;
174
+ hasDefault: false;
175
+ isPrimaryKey: false;
176
+ isAutoincrement: false;
177
+ hasRuntimeDefault: false;
178
+ enumValues: undefined;
179
+ baseColumn: never;
180
+ identity: undefined;
181
+ generated: undefined;
182
+ }, {}, {}>;
183
+ };
184
+ dialect: "sqlite";
185
+ }>;
186
+ export type JoinRequest = typeof joinRequests.$inferSelect;
187
+ export type NewJoinRequest = typeof joinRequests.$inferInsert;
188
+ //# sourceMappingURL=join-requests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-requests.d.ts","sourceRoot":"","sources":["../../src/schema/join-requests.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { sqliteTable, text, integer, index } from 'drizzle-orm/sqlite-core';
2
+ import { user } from './auth/index.js';
3
+ import { organization } from './auth/index.js';
4
+ /**
5
+ * join_requests — users requesting to join an organization.
6
+ *
7
+ * When a user not-yet-in-an-org wants access, they submit a join request.
8
+ * Org admins review (approve/deny). On approval the user is added as a member.
9
+ *
10
+ * Used by:
11
+ * - /join page (submission)
12
+ * - /api/join-requests/* (listing, counting, review)
13
+ * - notifications panel
14
+ */
15
+ export const joinRequests = sqliteTable('join_requests', {
16
+ id: text('id').primaryKey(),
17
+ userId: text('user_id')
18
+ .notNull()
19
+ .references(() => user.id, { onDelete: 'cascade' }),
20
+ orgId: text('org_id')
21
+ .notNull()
22
+ .references(() => organization.id, { onDelete: 'cascade' }),
23
+ email: text('email').notNull(),
24
+ message: text('message'),
25
+ status: text('status', { enum: ['pending', 'approved', 'denied'] })
26
+ .notNull()
27
+ .default('pending'),
28
+ reviewedBy: text('reviewed_by'),
29
+ reviewedAt: integer('reviewed_at'),
30
+ createdAt: integer('created_at').notNull(),
31
+ }, (t) => [
32
+ index('idx_join_requests_user').on(t.userId),
33
+ index('idx_join_requests_org_status').on(t.orgId, t.status),
34
+ ]);
35
+ //# sourceMappingURL=join-requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-requests.js","sourceRoot":"","sources":["../../src/schema/join-requests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CACrC,eAAe,EACf;IACE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;SACpB,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;SAClB,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC7D,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;IACxB,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;SAChE,OAAO,EAAE;SACT,OAAO,CAAC,SAAS,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC,aAAa,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,EACD,CAAC,CAAC,EAAE,EAAE,CAAC;IACL,KAAK,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5C,KAAK,CAAC,8BAA8B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;CAC5D,CACF,CAAC"}
@@ -195,7 +195,7 @@ export declare const personalAgents: import("drizzle-orm/sqlite-core").SQLiteTab
195
195
  tableName: "personal_agents";
196
196
  dataType: "string";
197
197
  columnType: "SQLiteText";
198
- data: "pending" | "provisioning" | "active" | "error";
198
+ data: "active" | "error" | "pending" | "provisioning";
199
199
  driverParam: string;
200
200
  notNull: true;
201
201
  hasDefault: true;
@@ -81,7 +81,7 @@ export declare const reliabilityEvents: import("drizzle-orm/sqlite-core").SQLite
81
81
  tableName: "reliability_events";
82
82
  dataType: "string";
83
83
  columnType: "SQLiteText";
84
- data: "general" | "cron" | "browser" | "timezone" | "auth" | "skill" | "agent" | "gateway";
84
+ data: "general" | "gateway" | "cron" | "browser" | "timezone" | "auth" | "skill" | "agent";
85
85
  driverParam: string;
86
86
  notNull: true;
87
87
  hasDefault: false;
@@ -119,7 +119,7 @@ export declare const skillExecutionStats: import("drizzle-orm/sqlite-core").SQLi
119
119
  tableName: "skill_execution_stats";
120
120
  dataType: "string";
121
121
  columnType: "SQLiteText";
122
- data: "ok" | "error" | "auth_error" | "timeout";
122
+ data: "ok" | "auth_error" | "timeout" | "error";
123
123
  driverParam: string;
124
124
  notNull: true;
125
125
  hasDefault: false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minion-stack/db",
3
- "version": "0.3.1",
3
+ "version": "0.6.0",
4
4
  "description": "Drizzle ORM schema for the Minion shared database (LibSQL/Turso).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/pg/schema/index.d.ts",
29
29
  "import": "./dist/pg/schema/index.js"
30
30
  },
31
+ "./crypto": {
32
+ "types": "./dist/crypto.d.ts",
33
+ "import": "./dist/crypto.js"
34
+ },
31
35
  "./relations": {
32
36
  "types": "./dist/relations.d.ts",
33
37
  "import": "./dist/relations.js"
@@ -45,24 +49,23 @@
45
49
  "src",
46
50
  "README.md"
47
51
  ],
48
- "scripts": {
49
- "build": "tsc",
50
- "prepublishOnly": "tsc",
51
- "typecheck": "tsc --noEmit",
52
- "lint": "oxlint src",
53
- "db:pg:generate": "drizzle-kit generate --config=drizzle.pg.config.ts",
54
- "test": "vitest run"
55
- },
56
52
  "peerDependencies": {
57
53
  "drizzle-orm": ">=0.45.0"
58
54
  },
59
55
  "devDependencies": {
60
- "@minion-stack/tsconfig": "workspace:*",
61
56
  "@paralleldrive/cuid2": "^3.3.0",
62
57
  "drizzle-kit": "^0.31.9",
63
58
  "drizzle-orm": "^0.45.1",
64
59
  "oxlint": "^1.66.0",
65
60
  "typescript": "^5.0.0",
66
- "vitest": "^2.1.9"
61
+ "vitest": "^2.1.9",
62
+ "@minion-stack/tsconfig": "0.1.0"
63
+ },
64
+ "scripts": {
65
+ "build": "tsc",
66
+ "typecheck": "tsc --noEmit",
67
+ "lint": "oxlint src",
68
+ "db:pg:generate": "drizzle-kit generate --config=drizzle.pg.config.ts",
69
+ "test": "vitest run"
67
70
  }
68
- }
71
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ sealSecret,
4
+ openSecret,
5
+ encrypt,
6
+ decrypt,
7
+ encryptToken,
8
+ decryptToken,
9
+ } from "./crypto.js";
10
+
11
+ describe("canonical crypto", () => {
12
+ it("seal/open roundtrips", () => {
13
+ const { ciphertext, iv } = sealSecret("hunter2");
14
+ expect(ciphertext).not.toContain("hunter2");
15
+ expect(openSecret(ciphertext, iv)).toBe("hunter2");
16
+ });
17
+
18
+ it("encrypt/decrypt are aliases of seal/open", () => {
19
+ const { ciphertext, iv } = encrypt("s3cret");
20
+ expect(decrypt(ciphertext, iv)).toBe("s3cret");
21
+ });
22
+
23
+ it("encryptToken returns { encrypted, iv } and decryptToken roundtrips", () => {
24
+ const { encrypted, iv } = encryptToken("tok-abc");
25
+ expect(decryptToken(encrypted, iv)).toBe("tok-abc");
26
+ });
27
+
28
+ it("openSecret throws on a tampered ciphertext (GCM auth)", () => {
29
+ const { ciphertext, iv } = sealSecret("x");
30
+ const tampered = (ciphertext[0] === "a" ? "b" : "a") + ciphertext.slice(1);
31
+ expect(() => openSecret(tampered, iv)).toThrow();
32
+ });
33
+ });
package/src/crypto.ts ADDED
@@ -0,0 +1,73 @@
1
+ // Canonical app-level secret encryption for the Minion stack (R7 of
2
+ // specs/2026-05-26-auth-token-simplification.md). AES-256-GCM, dialect-agnostic
3
+ // — consumed by the PG identity path (sealSecret/openSecret) and re-exported by
4
+ // minion_hub's crypto.ts (encrypt/decrypt/encryptToken/decryptToken) so there is
5
+ // ONE implementation and one key-derivation path instead of byte-matched copies.
6
+ //
7
+ // Layout (MUST stay stable — existing ciphertext at rest depends on it):
8
+ // key = scryptSync(ENCRYPTION_KEY, 'minion-hub-salt', 32)
9
+ // ciphertext = hex(encrypted || authTag) (16-byte GCM tag LAST)
10
+ // iv = hex(12 random bytes), stored separately
11
+
12
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
13
+
14
+ const ALGORITHM = "aes-256-gcm";
15
+ const IV_BYTES = 12;
16
+ const AUTH_TAG_BYTES = 16;
17
+
18
+ let cachedKey: Buffer | null = null;
19
+ function key(): Buffer {
20
+ if (cachedKey) return cachedKey;
21
+ const raw = process.env.ENCRYPTION_KEY;
22
+ if (!raw) {
23
+ if (process.env.NODE_ENV === "production") {
24
+ throw new Error("ENCRYPTION_KEY environment variable must be set in production");
25
+ }
26
+ // Dev-only fallback — never used in production.
27
+ cachedKey = scryptSync("minion-hub-dev-key", "minion-hub-salt", 32);
28
+ return cachedKey;
29
+ }
30
+ cachedKey = scryptSync(raw, "minion-hub-salt", 32);
31
+ return cachedKey;
32
+ }
33
+
34
+ /** Seal plaintext → { ciphertext, iv }. ciphertext = hex(encrypted || authTag). */
35
+ export function sealSecret(plaintext: string): { ciphertext: string; iv: string } {
36
+ const iv = randomBytes(IV_BYTES);
37
+ const cipher = createCipheriv(ALGORITHM, key(), iv);
38
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
39
+ const authTag = cipher.getAuthTag();
40
+ const combined = Buffer.concat([encrypted, authTag]);
41
+ return { ciphertext: combined.toString("hex"), iv: iv.toString("hex") };
42
+ }
43
+
44
+ /** Open hex(encrypted || authTag) + hex(iv) → plaintext. Throws on auth failure. */
45
+ export function openSecret(ciphertext: string, iv: string): string {
46
+ const combined = Buffer.from(ciphertext, "hex");
47
+ const encrypted = combined.subarray(0, combined.length - AUTH_TAG_BYTES);
48
+ const authTag = combined.subarray(combined.length - AUTH_TAG_BYTES);
49
+ const decipher = createDecipheriv(ALGORITHM, key(), Buffer.from(iv, "hex"));
50
+ decipher.setAuthTag(authTag);
51
+ return decipher.update(encrypted) + decipher.final("utf8");
52
+ }
53
+
54
+ // --- minion_hub-compatible aliases -------------------------------------------
55
+ // Hub's crypto.ts historically exported these names; keeping them lets hub become
56
+ // a thin re-export of this module without touching its many call sites.
57
+
58
+ /** Alias of {@link sealSecret}. */
59
+ export const encrypt = sealSecret;
60
+
61
+ /** Alias of {@link openSecret}. */
62
+ export const decrypt = openSecret;
63
+
64
+ /** Seal a token → { encrypted, iv } (hub's field name for the ciphertext). */
65
+ export function encryptToken(token: string): { encrypted: string; iv: string } {
66
+ const { ciphertext, iv } = sealSecret(token);
67
+ return { encrypted: ciphertext, iv };
68
+ }
69
+
70
+ /** Open a sealed token. */
71
+ export function decryptToken(encrypted: string, iv: string): string {
72
+ return openSecret(encrypted, iv);
73
+ }
package/src/pg/crypto.ts CHANGED
@@ -1,44 +1,4 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
2
-
3
- const ALGORITHM = 'aes-256-gcm';
4
- const IV_BYTES = 12;
5
- const AUTH_TAG_BYTES = 16;
6
-
7
- // MUST match minion_hub/src/server/auth/crypto.ts so hub + site interoperate:
8
- // key = scryptSync(ENCRYPTION_KEY, 'minion-hub-salt', 32)
9
- // ciphertext (hex) = encrypted || authTag (16-byte tag LAST)
10
- // iv (hex) = 12 random bytes, stored separately
11
- let cachedKey: Buffer | null = null;
12
- function key(): Buffer {
13
- if (cachedKey) return cachedKey;
14
- const raw = process.env.ENCRYPTION_KEY;
15
- if (!raw) {
16
- if (process.env.NODE_ENV === 'production') {
17
- throw new Error('ENCRYPTION_KEY environment variable must be set in production');
18
- }
19
- cachedKey = scryptSync('minion-hub-dev-key', 'minion-hub-salt', 32);
20
- return cachedKey;
21
- }
22
- cachedKey = scryptSync(raw, 'minion-hub-salt', 32);
23
- return cachedKey;
24
- }
25
-
26
- /** Seal plaintext → { ciphertext, iv }. ciphertext = hex(encrypted || authTag). */
27
- export function sealSecret(plaintext: string): { ciphertext: string; iv: string } {
28
- const iv = randomBytes(IV_BYTES);
29
- const cipher = createCipheriv(ALGORITHM, key(), iv);
30
- const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
31
- const authTag = cipher.getAuthTag();
32
- const combined = Buffer.concat([encrypted, authTag]);
33
- return { ciphertext: combined.toString('hex'), iv: iv.toString('hex') };
34
- }
35
-
36
- /** Open hex(encrypted || authTag) + hex(iv) → plaintext. Throws on auth failure. */
37
- export function openSecret(ciphertext: string, iv: string): string {
38
- const combined = Buffer.from(ciphertext, 'hex');
39
- const encrypted = combined.subarray(0, combined.length - AUTH_TAG_BYTES);
40
- const authTag = combined.subarray(combined.length - AUTH_TAG_BYTES);
41
- const decipher = createDecipheriv(ALGORITHM, key(), Buffer.from(iv, 'hex'));
42
- decipher.setAuthTag(authTag);
43
- return decipher.update(encrypted) + decipher.final('utf8');
44
- }
1
+ // Re-export of the canonical crypto module (../crypto.ts). Kept as a stable
2
+ // subpath for existing importers of the PG identity path; the implementation
3
+ // lives in one place now (R7 of specs/2026-05-26-auth-token-simplification.md).
4
+ export { sealSecret, openSecret } from "../crypto.js";
@@ -0,0 +1,30 @@
1
+ import { pgTable, uuid, text, integer, timestamp, index, primaryKey } from 'drizzle-orm/pg-core';
2
+ import { profiles } from './profiles.js';
3
+
4
+ /** User-defined agent groupings. Mirrors Turso `agent_groups`. user_id → profile_id, tenant_id → organizations.id. */
5
+ export const agentGroups = pgTable(
6
+ 'agent_groups',
7
+ {
8
+ id: text('id').primaryKey(),
9
+ profileId: uuid('profile_id')
10
+ .notNull()
11
+ .references(() => profiles.id, { onDelete: 'cascade' }),
12
+ tenantId: uuid('tenant_id').notNull(),
13
+ name: text('name').notNull(),
14
+ sortOrder: integer('sort_order').default(0),
15
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
16
+ },
17
+ (t) => [index('idx_agent_groups_profile').on(t.profileId, t.tenantId)],
18
+ );
19
+
20
+ export const agentGroupMembers = pgTable(
21
+ 'agent_group_members',
22
+ {
23
+ groupId: text('group_id')
24
+ .notNull()
25
+ .references(() => agentGroups.id, { onDelete: 'cascade' }),
26
+ agentId: text('agent_id').notNull(),
27
+ sortOrder: integer('sort_order').default(0),
28
+ },
29
+ (t) => [primaryKey({ columns: [t.groupId, t.agentId] })],
30
+ );