@essentialai/cogent-server 3.4.2 → 3.4.3

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 (97) hide show
  1. package/dist/__tests__/helpers.d.ts +5 -2
  2. package/dist/__tests__/helpers.d.ts.map +1 -1
  3. package/dist/__tests__/helpers.js +11 -4
  4. package/dist/__tests__/helpers.js.map +1 -1
  5. package/dist/__tests__/services/session-store-contract.d.ts +17 -0
  6. package/dist/__tests__/services/session-store-contract.d.ts.map +1 -0
  7. package/dist/__tests__/services/session-store-contract.js +186 -0
  8. package/dist/__tests__/services/session-store-contract.js.map +1 -0
  9. package/dist/app.d.ts +9 -0
  10. package/dist/app.d.ts.map +1 -1
  11. package/dist/app.js +18 -2
  12. package/dist/app.js.map +1 -1
  13. package/dist/config.d.ts +14 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +44 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/contract/control-plane-contract.d.ts +93 -0
  18. package/dist/contract/control-plane-contract.d.ts.map +1 -0
  19. package/dist/contract/control-plane-contract.js +72 -0
  20. package/dist/contract/control-plane-contract.js.map +1 -0
  21. package/dist/db/index.d.ts +5 -0
  22. package/dist/db/index.d.ts.map +1 -0
  23. package/dist/db/index.js +3 -0
  24. package/dist/db/index.js.map +1 -0
  25. package/dist/db/migrate-cli.d.ts +2 -0
  26. package/dist/db/migrate-cli.d.ts.map +1 -0
  27. package/dist/db/migrate-cli.js +54 -0
  28. package/dist/db/migrate-cli.js.map +1 -0
  29. package/dist/db/migrate.d.ts +31 -0
  30. package/dist/db/migrate.d.ts.map +1 -0
  31. package/dist/db/migrate.js +98 -0
  32. package/dist/db/migrate.js.map +1 -0
  33. package/dist/db/migrations/0001_init_sessions.down.sql +4 -0
  34. package/dist/db/migrations/0001_init_sessions.up.sql +46 -0
  35. package/dist/db/migrations/0002_org_quotas.down.sql +2 -0
  36. package/dist/db/migrations/0002_org_quotas.up.sql +13 -0
  37. package/dist/db/pool.d.ts +39 -0
  38. package/dist/db/pool.d.ts.map +1 -0
  39. package/dist/db/pool.js +72 -0
  40. package/dist/db/pool.js.map +1 -0
  41. package/dist/index.js +33 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/middleware/auth.d.ts.map +1 -1
  44. package/dist/middleware/auth.js +32 -0
  45. package/dist/middleware/auth.js.map +1 -1
  46. package/dist/middleware/control-plane-auth.d.ts +17 -0
  47. package/dist/middleware/control-plane-auth.d.ts.map +1 -0
  48. package/dist/middleware/control-plane-auth.js +35 -0
  49. package/dist/middleware/control-plane-auth.js.map +1 -0
  50. package/dist/routes/control-plane.d.ts +20 -0
  51. package/dist/routes/control-plane.d.ts.map +1 -0
  52. package/dist/routes/control-plane.js +122 -0
  53. package/dist/routes/control-plane.js.map +1 -0
  54. package/dist/routes/messages.d.ts.map +1 -1
  55. package/dist/routes/messages.js +18 -0
  56. package/dist/routes/messages.js.map +1 -1
  57. package/dist/routes/poll.js +2 -2
  58. package/dist/routes/poll.js.map +1 -1
  59. package/dist/routes/sessions.d.ts +22 -1
  60. package/dist/routes/sessions.d.ts.map +1 -1
  61. package/dist/routes/sessions.js +99 -13
  62. package/dist/routes/sessions.js.map +1 -1
  63. package/dist/routes/validation-hook.d.ts +31 -0
  64. package/dist/routes/validation-hook.d.ts.map +1 -1
  65. package/dist/routes/validation-hook.js +3 -0
  66. package/dist/routes/validation-hook.js.map +1 -1
  67. package/dist/services/auth-service.d.ts +5 -45
  68. package/dist/services/auth-service.d.ts.map +1 -1
  69. package/dist/services/auth-service.js +5 -60
  70. package/dist/services/auth-service.js.map +1 -1
  71. package/dist/services/connection-manager.d.ts +15 -0
  72. package/dist/services/connection-manager.d.ts.map +1 -1
  73. package/dist/services/connection-manager.js +29 -0
  74. package/dist/services/connection-manager.js.map +1 -1
  75. package/dist/services/join-rate-limiter.d.ts +50 -0
  76. package/dist/services/join-rate-limiter.d.ts.map +1 -0
  77. package/dist/services/join-rate-limiter.js +89 -0
  78. package/dist/services/join-rate-limiter.js.map +1 -0
  79. package/dist/services/obs-log.d.ts +51 -0
  80. package/dist/services/obs-log.d.ts.map +1 -0
  81. package/dist/services/obs-log.js +93 -0
  82. package/dist/services/obs-log.js.map +1 -0
  83. package/dist/services/session-store-memory.d.ts +60 -0
  84. package/dist/services/session-store-memory.d.ts.map +1 -0
  85. package/dist/services/session-store-memory.js +189 -0
  86. package/dist/services/session-store-memory.js.map +1 -0
  87. package/dist/services/session-store-postgres.d.ts +60 -0
  88. package/dist/services/session-store-postgres.d.ts.map +1 -0
  89. package/dist/services/session-store-postgres.js +393 -0
  90. package/dist/services/session-store-postgres.js.map +1 -0
  91. package/dist/services/session-store.d.ts +73 -5
  92. package/dist/services/session-store.d.ts.map +1 -1
  93. package/dist/services/session-store.js +62 -16
  94. package/dist/services/session-store.js.map +1 -1
  95. package/dist/types.d.ts +13 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/package.json +11 -6
@@ -0,0 +1,393 @@
1
+ import { BridgeError, ErrorCode, isValidSessionId } from "@essentialai/cogent";
2
+ import { withTransaction } from "../db/pool.js";
3
+ import { isUpToDate } from "../db/migrate.js";
4
+ import { labelIndexKey } from "./session-store.js";
5
+ import { logOrgEvent } from "./obs-log.js";
6
+ /**
7
+ * Postgres implementation of {@link SessionStore} (Business Edition, FR35),
8
+ * JSONB-hybrid: queryable/unique/auth scalars are columns; the rest of the
9
+ * session document lives in a `state jsonb` column — a 1:1 mirror of the file
10
+ * store's per-session JSON, so `updateSession` is a whole-document UPDATE.
11
+ *
12
+ * The synchronous index methods (`getSessionIdByLabel`, `isLabelTaken`,
13
+ * `getTokenIndex`) are served from in-memory caches rebuilt on `init()` and kept
14
+ * in sync on writes — exactly as `FileSessionStore` does. This is correct for
15
+ * the single-node MVP (all writes go through this instance); multi-node (vNext)
16
+ * will need cache invalidation or a shared lookup. `getSessionByTokenHash`
17
+ * queries the DB directly so bearer-token auth is always authoritative.
18
+ *
19
+ * The pool is injected (DI) and owned by the caller (index.ts closes it).
20
+ */
21
+ export class PostgresSessionStore {
22
+ pool;
23
+ maxMessagesPerSession;
24
+ /** label-scope key -> sessionId (mirrors FileSessionStore.labelIndex). */
25
+ labelIndex = new Map();
26
+ /** SHA-256 tokenHash -> sessionId. */
27
+ tokenIndex = new Map();
28
+ constructor(pool, maxMessagesPerSession) {
29
+ this.pool = pool;
30
+ this.maxMessagesPerSession = maxMessagesPerSession;
31
+ }
32
+ async init() {
33
+ if (!(await isUpToDate(this.pool))) {
34
+ throw new BridgeError(ErrorCode.STARTUP_FAILED, "Database schema is not migrated", "Run `npm run db:migrate` against COGENT_SERVER_DATABASE_URL before starting");
35
+ }
36
+ // Rebuild the sync-lookup caches from the DB.
37
+ const tokens = await this.pool.query("SELECT token_hash, session_id FROM tokens");
38
+ for (const r of tokens.rows)
39
+ this.tokenIndex.set(r.token_hash, r.session_id);
40
+ const labels = await this.pool.query("SELECT session_id, label, org_id_hash, org_scope FROM sessions WHERE label IS NOT NULL");
41
+ for (const r of labels.rows) {
42
+ // Fail-closed: never index a legacy business session lacking org_scope
43
+ // under the public bare key (mirrors FileSessionStore._rebuildLabelIndex).
44
+ if (r.org_id_hash !== null && r.org_scope === null)
45
+ continue;
46
+ this.labelIndex.set(labelIndexKey(r.label, r.org_scope ?? undefined), r.session_id);
47
+ }
48
+ }
49
+ async createSession(sessionId, label, secretHash, tokenEntry, creatorIp, org) {
50
+ const createdAt = new Date().toISOString();
51
+ const initialDoc = { peers: {}, messages: [], peerEvents: [] };
52
+ // Returned from the tx, logged AFTER commit so an "admitted" quota_check is
53
+ // never emitted for a create that later rolls back. (Rejections are logged
54
+ // inside the tx — the rejection IS the committed outcome.)
55
+ const channelAdmit = await withTransaction(this.pool, async (client) => {
56
+ let admit = null;
57
+ // Atomic per-org channel quota (FR7/FR8). Row-lock the org's quota row so
58
+ // concurrent creates for the same org serialize: exactly one can cross the
59
+ // limit. A missing quota row = not-yet-provisioned (E3) -> no cap. The
60
+ // lock_timeout bounds FOR UPDATE contention (no indefinite block).
61
+ if (org) {
62
+ await client.query("SET LOCAL lock_timeout = '3s'");
63
+ try {
64
+ const qr = await client.query("SELECT max_channels FROM org_quotas WHERE org_scope = $1 FOR UPDATE", [org.scope]);
65
+ if (qr.rowCount && qr.rowCount > 0) {
66
+ const cnt = await client.query("SELECT count(*)::int AS n FROM sessions WHERE org_scope = $1", [org.scope]);
67
+ const atLimit = cnt.rows[0].n >= qr.rows[0].max_channels;
68
+ if (atLimit) {
69
+ logOrgEvent({
70
+ event: "quota_check",
71
+ orgScope: org.scope,
72
+ sessionId,
73
+ outcome: "rejected",
74
+ detail: { quota: "channel", used: cnt.rows[0].n, limit: qr.rows[0].max_channels },
75
+ });
76
+ throw new BridgeError(ErrorCode.SESSION_FULL, `Org channel quota reached (${cnt.rows[0].n}/${qr.rows[0].max_channels})`, "Upgrade your tier or delete a channel before creating another");
77
+ }
78
+ admit = { used: cnt.rows[0].n, limit: qr.rows[0].max_channels };
79
+ }
80
+ }
81
+ catch (err) {
82
+ // Surface lock_timeout/deadlock as a clean retryable error; rethrow
83
+ // everything else (incl. the SESSION_FULL above) unchanged.
84
+ this._mapLockError(err);
85
+ }
86
+ }
87
+ try {
88
+ await client.query(`INSERT INTO sessions
89
+ (session_id, label, secret_hash, org_id_hash, org_scope, creator_ip, created_at, state)
90
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`, [
91
+ sessionId,
92
+ label ?? null,
93
+ secretHash,
94
+ org?.idHash ?? null,
95
+ org?.scope ?? null,
96
+ creatorIp || null, // drop empty string, like the file store
97
+ createdAt,
98
+ JSON.stringify(initialDoc),
99
+ ]);
100
+ }
101
+ catch (err) {
102
+ this._mapCreateError(err, sessionId, label);
103
+ }
104
+ await client.query("INSERT INTO tokens (token_hash, session_id, peer_id, created_at) VALUES ($1, $2, $3, $4)", [tokenEntry.tokenHash, sessionId, tokenEntry.peerId ?? null, tokenEntry.createdAt]);
105
+ return admit;
106
+ });
107
+ // Update caches AFTER the commit.
108
+ this.tokenIndex.set(tokenEntry.tokenHash, sessionId);
109
+ if (label !== undefined)
110
+ this.labelIndex.set(labelIndexKey(label, org?.scope), sessionId);
111
+ // Admitted quota_check logged post-commit (accurate: the channel exists now).
112
+ if (org && channelAdmit) {
113
+ logOrgEvent({
114
+ event: "quota_check",
115
+ orgScope: org.scope,
116
+ sessionId,
117
+ outcome: "admitted",
118
+ detail: { quota: "channel", used: channelAdmit.used, limit: channelAdmit.limit },
119
+ });
120
+ }
121
+ const state = {
122
+ sessionId,
123
+ ...(label !== undefined ? { label } : {}),
124
+ secretHash,
125
+ ...(org !== undefined ? { orgIdHash: org.idHash, orgScope: org.scope } : {}),
126
+ tokens: [tokenEntry],
127
+ createdAt,
128
+ ...(creatorIp ? { creatorIp } : {}),
129
+ peers: {},
130
+ messages: [],
131
+ peerEvents: [],
132
+ };
133
+ return state;
134
+ }
135
+ async getSession(sessionId) {
136
+ // A malformed (non-UUID) id can't exist in a uuid column; return not-found
137
+ // rather than letting Postgres raise 22P02 (parity with the file store).
138
+ if (!isValidSessionId(sessionId))
139
+ return null;
140
+ const res = await this.pool.query("SELECT * FROM sessions WHERE session_id = $1", [sessionId]);
141
+ if (res.rowCount === 0)
142
+ return null;
143
+ const tokens = await this.pool.query("SELECT token_hash, peer_id, created_at FROM tokens WHERE session_id = $1 ORDER BY created_at ASC", [sessionId]);
144
+ return this._rowToState(res.rows[0], tokens.rows);
145
+ }
146
+ async getSessionByTokenHash(tokenHash) {
147
+ // Authoritative DB lookup (not the cache) — auth must never read stale.
148
+ const res = await this.pool.query("SELECT session_id FROM tokens WHERE token_hash = $1", [tokenHash]);
149
+ if (res.rowCount === 0)
150
+ return null;
151
+ const sessionId = res.rows[0].session_id;
152
+ const state = await this.getSession(sessionId);
153
+ if (!state)
154
+ return null;
155
+ return { sessionId, state };
156
+ }
157
+ async updateSession(sessionId, updater) {
158
+ if (!isValidSessionId(sessionId)) {
159
+ throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`, "Check the session ID or create a new session");
160
+ }
161
+ const { state: result, admit: agentAdmit } = await withTransaction(this.pool, async (client) => {
162
+ let admit = null;
163
+ const res = await client.query("SELECT * FROM sessions WHERE session_id = $1 FOR UPDATE", [sessionId]);
164
+ if (res.rowCount === 0) {
165
+ throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`, "Check the session ID or create a new session");
166
+ }
167
+ const tokens = await client.query("SELECT token_hash, peer_id, created_at FROM tokens WHERE session_id = $1 ORDER BY created_at ASC", [sessionId]);
168
+ const current = this._rowToState(res.rows[0], tokens.rows);
169
+ const updated = updater(structuredClone(current));
170
+ if (updated.messages.length > this.maxMessagesPerSession) {
171
+ updated.messages = updated.messages.slice(-this.maxMessagesPerSession);
172
+ }
173
+ // Atomic per-org AGENT quota (FR7/FR8). Enforce ONLY when this update adds
174
+ // a net-new peer to a business (org-scoped) session — peer registration is
175
+ // the single mutation that consumes an agent slot. We already hold this
176
+ // session's row lock (FOR UPDATE above); additionally row-lock the org's
177
+ // quota row so concurrent registrations across the org's channels
178
+ // serialize — exactly one can cross the limit (parity with the channel
179
+ // quota in createSession). Release is inherent: deregister drops a peer →
180
+ // the count falls. A missing quota row = not-yet-provisioned (E3) → no cap.
181
+ // `addsPeer` uses the net key-count delta, which is exact here because the
182
+ // only updater that grows `peers` is peer registration, and it adds exactly
183
+ // one key (peers.ts). A heartbeat (lastSeenAt), message append, or
184
+ // deregister never grows the count, so the check is skipped for them. The
185
+ // enforced `total` below is the absolute post-state count (not a delta), so
186
+ // even a hypothetical multi-peer churn could never cross the cap.
187
+ const orgScope = res.rows[0].org_scope;
188
+ const addsPeer = orgScope !== null &&
189
+ Object.keys(updated.peers).length > Object.keys(current.peers).length;
190
+ if (addsPeer) {
191
+ await client.query("SET LOCAL lock_timeout = '3s'");
192
+ try {
193
+ const qr = await client.query("SELECT max_agents FROM org_quotas WHERE org_scope = $1 FOR UPDATE", [orgScope]);
194
+ if (qr.rowCount && qr.rowCount > 0) {
195
+ // Count agents in the org's OTHER channels from the DB (this session's
196
+ // new peers aren't written yet), then add this session's post-update
197
+ // peer count. COALESCE guards a null/absent peers document.
198
+ const others = await client.query(`SELECT COALESCE(
199
+ SUM((SELECT count(*) FROM jsonb_object_keys(COALESCE(s.state->'peers', '{}'::jsonb)))),
200
+ 0)::int AS n
201
+ FROM sessions s
202
+ WHERE s.org_scope = $1 AND s.session_id <> $2`, [orgScope, sessionId]);
203
+ const total = others.rows[0].n + Object.keys(updated.peers).length;
204
+ const atLimit = total > qr.rows[0].max_agents;
205
+ if (atLimit) {
206
+ logOrgEvent({
207
+ event: "quota_check",
208
+ orgScope,
209
+ sessionId,
210
+ outcome: "rejected",
211
+ detail: { quota: "agent", used: total, limit: qr.rows[0].max_agents },
212
+ });
213
+ throw new BridgeError(ErrorCode.SESSION_FULL, `Org agent quota reached (${total}/${qr.rows[0].max_agents})`, "Upgrade your tier or disconnect an agent before registering another");
214
+ }
215
+ admit = { used: total, limit: qr.rows[0].max_agents, orgScope: orgScope };
216
+ }
217
+ }
218
+ catch (err) {
219
+ // Surface lock_timeout/deadlock as a clean retryable error; rethrow
220
+ // everything else (incl. the SESSION_FULL above) unchanged.
221
+ this._mapLockError(err);
222
+ }
223
+ }
224
+ // Persist the document (everything except tokens, which addToken owns) +
225
+ // the scalar columns, in case the updater changed them.
226
+ const doc = this._docFromState(updated);
227
+ await client.query(`UPDATE sessions
228
+ SET label = $2, secret_hash = $3, org_id_hash = $4, org_scope = $5,
229
+ creator_ip = $6, state = $7::jsonb
230
+ WHERE session_id = $1`, [
231
+ sessionId,
232
+ updated.label ?? null,
233
+ updated.secretHash,
234
+ updated.orgIdHash ?? null,
235
+ updated.orgScope ?? null,
236
+ updated.creatorIp ?? null,
237
+ JSON.stringify(doc),
238
+ ]);
239
+ return { state: structuredClone(updated), admit };
240
+ });
241
+ // Admitted agent quota_check logged post-commit (the peer is persisted now).
242
+ if (agentAdmit) {
243
+ logOrgEvent({
244
+ event: "quota_check",
245
+ orgScope: agentAdmit.orgScope,
246
+ sessionId,
247
+ outcome: "admitted",
248
+ detail: { quota: "agent", used: agentAdmit.used, limit: agentAdmit.limit },
249
+ });
250
+ }
251
+ return result;
252
+ }
253
+ async addToken(sessionId, tokenEntry) {
254
+ if (!isValidSessionId(sessionId)) {
255
+ throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`, "Check the session ID or create a new session");
256
+ }
257
+ try {
258
+ await this.pool.query(`INSERT INTO tokens (token_hash, session_id, peer_id, created_at)
259
+ VALUES ($1, $2, $3, $4) ON CONFLICT (token_hash) DO NOTHING`, [tokenEntry.tokenHash, sessionId, tokenEntry.peerId ?? null, tokenEntry.createdAt]);
260
+ }
261
+ catch (err) {
262
+ // FK violation => the session does not exist (parity with file store).
263
+ if (err.code === "23503") {
264
+ throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`, "Check the session ID or create a new session");
265
+ }
266
+ throw err;
267
+ }
268
+ this.tokenIndex.set(tokenEntry.tokenHash, sessionId);
269
+ }
270
+ async deleteSession(sessionId) {
271
+ if (!isValidSessionId(sessionId))
272
+ return; // no-op like the file store
273
+ const res = await this.pool.query("DELETE FROM sessions WHERE session_id = $1 RETURNING label, org_scope", [sessionId]);
274
+ if (res.rowCount && res.rowCount > 0) {
275
+ const { label, org_scope } = res.rows[0];
276
+ if (label !== null) {
277
+ this.labelIndex.delete(labelIndexKey(label, org_scope ?? undefined));
278
+ }
279
+ }
280
+ // tokens were cascade-deleted; purge their cache entries for this session.
281
+ for (const [hash, sid] of this.tokenIndex) {
282
+ if (sid === sessionId)
283
+ this.tokenIndex.delete(hash);
284
+ }
285
+ }
286
+ async listSessions() {
287
+ const res = await this.pool.query("SELECT session_id FROM sessions");
288
+ return res.rows.map((r) => r.session_id);
289
+ }
290
+ async countAgentsByOrgScope(orgScope) {
291
+ // Mirrors EXACTLY the per-org agent-quota count in updateSession (sum of
292
+ // peer keys across the org's sessions) — minus the self-exclusion, since
293
+ // here we want the full current total. Keeps usage == enforcement.
294
+ const res = await this.pool.query(`SELECT COALESCE(
295
+ SUM((SELECT count(*) FROM jsonb_object_keys(COALESCE(s.state->'peers', '{}'::jsonb)))),
296
+ 0)::int AS n
297
+ FROM sessions s
298
+ WHERE s.org_scope = $1`, [orgScope]);
299
+ return res.rows[0].n;
300
+ }
301
+ async setOrgQuota(orgScope, tier, maxChannels, maxAgents) {
302
+ // Upsert the org's enforced quota row (Story 5.1, FR20). The CP owns the
303
+ // tier→limits mapping; the relay persists what it enforces (channel quota in
304
+ // createSession, agent quota in updateSession — both row-lock this row). Idempotent:
305
+ // a tier change (5.7) re-sends and overwrites. updated_at tracks the last push.
306
+ await this.pool.query(`INSERT INTO org_quotas (org_scope, tier, max_channels, max_agents, updated_at)
307
+ VALUES ($1, $2, $3, $4, now())
308
+ ON CONFLICT (org_scope) DO UPDATE SET
309
+ tier = EXCLUDED.tier,
310
+ max_channels = EXCLUDED.max_channels,
311
+ max_agents = EXCLUDED.max_agents,
312
+ updated_at = now()`, [orgScope, tier, maxChannels, maxAgents]);
313
+ }
314
+ getTokenIndex() {
315
+ return this.tokenIndex;
316
+ }
317
+ getSessionIdByLabel(label, orgScope) {
318
+ return this.labelIndex.get(labelIndexKey(label, orgScope)) ?? null;
319
+ }
320
+ isLabelTaken(label, orgScope) {
321
+ return this.labelIndex.has(labelIndexKey(label, orgScope));
322
+ }
323
+ async getGlobalMessageCount() {
324
+ const res = await this.pool.query("SELECT total_messages FROM global_stats WHERE id = true");
325
+ return res.rowCount ? Number(res.rows[0].total_messages) : 0;
326
+ }
327
+ async incrementGlobalMessageCount() {
328
+ // Atomic in SQL — no read-modify-write race.
329
+ await this.pool.query("UPDATE global_stats SET total_messages = total_messages + 1 WHERE id = true");
330
+ }
331
+ // --- Private helpers ---
332
+ /** Assemble a SessionFileState from a sessions row + its token rows. */
333
+ _rowToState(row, tokenRows) {
334
+ const doc = row.state ?? {};
335
+ return {
336
+ sessionId: row.session_id,
337
+ ...(row.label !== null ? { label: row.label } : {}),
338
+ secretHash: row.secret_hash,
339
+ ...(row.org_id_hash !== null ? { orgIdHash: row.org_id_hash } : {}),
340
+ ...(row.org_scope !== null ? { orgScope: row.org_scope } : {}),
341
+ tokens: tokenRows.map((t) => ({
342
+ tokenHash: t.token_hash,
343
+ createdAt: t.created_at instanceof Date ? t.created_at.toISOString() : String(t.created_at),
344
+ ...(t.peer_id !== null ? { peerId: t.peer_id } : {}),
345
+ })),
346
+ createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at),
347
+ ...(row.creator_ip !== null ? { creatorIp: row.creator_ip } : {}),
348
+ peers: doc.peers ?? {},
349
+ messages: doc.messages ?? [],
350
+ peerEvents: doc.peerEvents ?? [],
351
+ ...(doc.messageQueue !== undefined ? { messageQueue: doc.messageQueue } : {}),
352
+ ...(doc.channelMappings !== undefined ? { channelMappings: doc.channelMappings } : {}),
353
+ };
354
+ }
355
+ /** Extract the JSONB document (everything not in a scalar column or tokens). */
356
+ _docFromState(state) {
357
+ return {
358
+ peers: state.peers,
359
+ messages: state.messages,
360
+ peerEvents: state.peerEvents,
361
+ ...(state.messageQueue !== undefined ? { messageQueue: state.messageQueue } : {}),
362
+ ...(state.channelMappings !== undefined ? { channelMappings: state.channelMappings } : {}),
363
+ };
364
+ }
365
+ /**
366
+ * Map a row-lock contention failure to a clean, retryable BridgeError.
367
+ * 55P03 = lock_timeout fired (FOR UPDATE could not acquire within 3s);
368
+ * 40P01 = deadlock_detected. Both bound the critical section so a busy org
369
+ * can never hang a request indefinitely. Any other error is rethrown as-is
370
+ * (including the BridgeError SESSION_FULL raised inside the quota block).
371
+ */
372
+ _mapLockError(err) {
373
+ const code = err.code;
374
+ if (code === "55P03" || code === "40P01") {
375
+ throw new BridgeError(ErrorCode.LOCK_TIMEOUT, "Org quota check timed out under contention", "Too many concurrent requests for this org — retry in a moment");
376
+ }
377
+ throw err;
378
+ }
379
+ /** Map a pg INSERT error to the file store's BridgeError contract. */
380
+ _mapCreateError(err, sessionId, label) {
381
+ const e = err;
382
+ if (e.code === "23505") {
383
+ // Positively identify the constraint (don't assume label by elimination).
384
+ if (e.constraint === "sessions_scope_label_uniq") {
385
+ throw new BridgeError(ErrorCode.INVALID_INPUT, `Label "${label}" already in use`, "Choose a different label");
386
+ }
387
+ // sessions_pkey (or any other dup) -> the session id is taken.
388
+ throw new BridgeError(ErrorCode.INVALID_INPUT, `Session ${sessionId} already exists`, "Use a different session ID or join the existing session");
389
+ }
390
+ throw err;
391
+ }
392
+ }
393
+ //# sourceMappingURL=session-store-postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store-postgres.js","sourceRoot":"","sources":["../../src/services/session-store-postgres.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,73 @@
1
1
  import type { TokenEntry, SessionFileState } from "../types.js";
2
+ /** Business-org fields persisted on a session, derived from the Org_ID at create time. */
3
+ export interface SessionOrg {
4
+ /** bcrypt hash of the Org_ID (auth verification). */
5
+ idHash: string;
6
+ /** SHA-256 of the Org_ID (stable label-scoping key; never auth). */
7
+ scope: string;
8
+ }
9
+ /**
10
+ * Compute the label-index key. Free channels (no orgScope) use the BARE label
11
+ * so global free-channel uniqueness and resolution are unchanged (NFR6).
12
+ * Business channels namespace the label by their org scope, so two orgs can
13
+ * both hold `sprint-42` (Map keys compare by strict ===, so distinct strings
14
+ * are distinct keys).
15
+ */
16
+ export declare function labelIndexKey(label: string, orgScope?: string): string;
17
+ /**
18
+ * Persistence contract for relay sessions.
19
+ *
20
+ * Relay services depend on this interface, not a concrete store, so the
21
+ * single-tenant file store (`FileSessionStore`) and the multi-tenant Postgres
22
+ * store (`PostgresSessionStore`, Story 2.4b) are interchangeable via constructor
23
+ * injection. `InMemorySessionStore` provides a dependency-free implementation
24
+ * for fast tests and the pre-Postgres business path.
25
+ */
26
+ export interface SessionStore {
27
+ /** Initialize the store (rebuild indexes / open connections). */
28
+ init(): Promise<void>;
29
+ createSession(sessionId: string, label: string | undefined, secretHash: string, tokenEntry: TokenEntry, creatorIp?: string, org?: SessionOrg): Promise<SessionFileState>;
30
+ getSession(sessionId: string): Promise<SessionFileState | null>;
31
+ getSessionByTokenHash(tokenHash: string): Promise<{
32
+ sessionId: string;
33
+ state: SessionFileState;
34
+ } | null>;
35
+ updateSession(sessionId: string, updater: (state: SessionFileState) => SessionFileState): Promise<SessionFileState>;
36
+ addToken(sessionId: string, tokenEntry: TokenEntry): Promise<void>;
37
+ deleteSession(sessionId: string): Promise<void>;
38
+ listSessions(): Promise<string[]>;
39
+ /**
40
+ * Sum of registered peers across all sessions under `orgScope` (Business
41
+ * Edition). This is the SAME definition the per-org agent quota enforces (peer
42
+ * keys in each session's state document), so usage reporting and enforcement
43
+ * agree. Returns 0 for an unknown scope.
44
+ */
45
+ countAgentsByOrgScope(orgScope: string): Promise<number>;
46
+ /**
47
+ * Upsert an org's enforced tier quota (Business Edition, Epic 5 Story 5.1). The
48
+ * control plane pushes the tier-derived limits; the relay persists them (keyed by
49
+ * `orgScope`) and row-locks the row to enforce channel/agent caps atomically.
50
+ * Idempotent — a re-send (e.g. a tier change, 5.7) overwrites the limits. The free
51
+ * single-tenant store has no per-org quotas, so its implementation is a no-op.
52
+ */
53
+ setOrgQuota(orgScope: string, tier: string, maxChannels: number, maxAgents: number): Promise<void>;
54
+ /** Read-only token-hash -> sessionId index for auth middleware. */
55
+ getTokenIndex(): ReadonlyMap<string, string>;
56
+ getSessionIdByLabel(label: string, orgScope?: string): string | null;
57
+ isLabelTaken(label: string, orgScope?: string): boolean;
58
+ getGlobalMessageCount(): Promise<number>;
59
+ incrementGlobalMessageCount(): Promise<void>;
60
+ }
2
61
  /**
3
62
  * Per-session JSON file store with in-memory locking and atomic writes.
63
+ * The single-tenant (free relay) implementation of {@link SessionStore}.
4
64
  *
5
65
  * Each session is stored as `{sessionId}.json` in the state directory.
6
66
  * A per-session promise-chain mutex prevents lost updates from concurrent
7
67
  * requests targeting the same session. An in-memory Map provides O(1)
8
68
  * token-to-session lookup, rebuilt from disk on startup.
9
69
  */
10
- export declare class SessionStore {
70
+ export declare class FileSessionStore implements SessionStore {
11
71
  private readonly stateDir;
12
72
  private readonly maxMessagesPerSession;
13
73
  /** Per-session promise-chain mutex. */
@@ -26,7 +86,7 @@ export declare class SessionStore {
26
86
  * Create a new session with the given ID, label, secret hash, and initial token.
27
87
  * Throws INVALID_INPUT if a session file already exists for this ID.
28
88
  */
29
- createSession(sessionId: string, label: string | undefined, secretHash: string, tokenEntry: TokenEntry, creatorIp?: string): Promise<SessionFileState>;
89
+ createSession(sessionId: string, label: string | undefined, secretHash: string, tokenEntry: TokenEntry, creatorIp?: string, org?: SessionOrg): Promise<SessionFileState>;
30
90
  /**
31
91
  * Read a session by ID. Returns null if the session file does not exist.
32
92
  */
@@ -59,6 +119,8 @@ export declare class SessionStore {
59
119
  * List all session IDs by scanning .json files in the state directory.
60
120
  */
61
121
  listSessions(): Promise<string[]>;
122
+ countAgentsByOrgScope(orgScope: string): Promise<number>;
123
+ setOrgQuota(_orgScope: string, _tier: string, _maxChannels: number, _maxAgents: number): Promise<void>;
62
124
  /**
63
125
  * Return the token-to-session index (read-only) for auth middleware.
64
126
  */
@@ -67,11 +129,11 @@ export declare class SessionStore {
67
129
  * Look up a session ID by its human-readable label.
68
130
  * Returns null if no session has the given label.
69
131
  */
70
- getSessionIdByLabel(label: string): string | null;
132
+ getSessionIdByLabel(label: string, orgScope?: string): string | null;
71
133
  /**
72
- * Check whether a label is already in use by an existing session.
134
+ * Check whether a label is already in use within the given org scope.
73
135
  */
74
- isLabelTaken(label: string): boolean;
136
+ isLabelTaken(label: string, orgScope?: string): boolean;
75
137
  /**
76
138
  * Read and parse a session JSON file.
77
139
  * Throws SESSION_NOT_FOUND if the file does not exist.
@@ -95,6 +157,12 @@ export declare class SessionStore {
95
157
  private _rebuildTokenIndex;
96
158
  /**
97
159
  * Rebuild the label index entry for a single session.
160
+ *
161
+ * Fail-closed migration guard: a legacy business session (orgIdHash set but no
162
+ * orgScope — e.g. created before per-org scoping) must NOT be indexed under the
163
+ * public bare key, or its label would become cross-org enumerable via the
164
+ * public resolve endpoint. We cannot backfill orgScope (orgId is only stored
165
+ * hashed), so we skip indexing it; the channel remains usable by sessionId.
98
166
  */
99
167
  private _rebuildLabelIndex;
100
168
  /** Compute the file path for a session ID. */
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/services/session-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEhE;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAE/C,uCAAuC;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAE1D,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;gBAE5C,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM;IAK3D;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B3B;;;OAGG;IACG,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,UAAU,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,gBAAgB,CAAC;IA8C5B;;OAEG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAcrE;;;OAGG;IACG,qBAAqB,CACzB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,gBAAgB,CAAA;KAAE,GAAG,IAAI,CAAC;IAcjE;;;;OAIG;IACG,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,gBAAgB,GACrD,OAAO,CAAC,gBAAgB,CAAC;IAe5B;;OAEG;IACG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IASxE;;;;OAIG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CrD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAOvC;;OAEG;IACH,aAAa,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAI5C;;;OAGG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIjD;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAMpC;;;;OAIG;YACW,UAAU;IAgCxB;;;OAGG;YACW,WAAW;IAWzB;;;;OAIG;YACW,SAAS;IAmBvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B,8CAA8C;IAC9C,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,gBAAgB;IAIxB;;;OAGG;IACG,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;IAU9C;;;OAGG;IACG,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;YAOpC,iBAAiB;CAMhC"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/services/session-store.ts"],"names":[],"mappings":"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"}