@opengeni/db 0.27.7 → 0.27.9

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 (61) hide show
  1. package/dist/{chunk-IHPCI4GV.js → chunk-2JFRTKTG.js} +1 -1
  2. package/dist/chunk-2JFRTKTG.js.map +1 -0
  3. package/dist/{chunk-EX7CDSGH.js → chunk-NX2JNJJP.js} +13 -4
  4. package/dist/chunk-NX2JNJJP.js.map +1 -0
  5. package/dist/codex-token-resolver.d.ts +50 -8
  6. package/dist/connection-token-resolver.d.ts +45 -7
  7. package/dist/database.d.ts +137 -0
  8. package/dist/index.d.ts +25 -154
  9. package/dist/index.js +2796 -2634
  10. package/dist/index.js.map +1 -1
  11. package/dist/insights.d.ts +1 -1
  12. package/dist/memory-governance.d.ts +1 -1
  13. package/dist/new-session-drafts.d.ts +1 -1
  14. package/dist/preference-registry.d.ts +1 -1
  15. package/dist/provision-roles.d.ts +1 -1
  16. package/dist/provision-roles.js +1 -1
  17. package/dist/runtime-posture.d.ts +1 -1
  18. package/dist/schema.d.ts +44 -0
  19. package/dist/schema.js +1 -1
  20. package/dist/scoped-knowledge.d.ts +1 -1
  21. package/dist/session-control.d.ts +2 -1
  22. package/dist/session-queue-commands.d.ts +1 -1
  23. package/dist/session-realtime-context.d.ts +1 -1
  24. package/dist/session-realtime-ledger.d.ts +1 -1
  25. package/dist/session-realtime-mirror.d.ts +1 -1
  26. package/dist/session-realtime-state.d.ts +1 -1
  27. package/dist/session-realtime-terminal.d.ts +1 -1
  28. package/dist/session-realtime.d.ts +1 -1
  29. package/dist/session-tool-call-settlement.d.ts +1 -1
  30. package/dist/turn-initiator.d.ts +1 -1
  31. package/dist/workspace-artifacts.d.ts +1 -1
  32. package/dist/workspace-instruction-policies.d.ts +1 -1
  33. package/drizzle/0170_session_control_wake_revision.sql +133 -0
  34. package/drizzle/0171_social_connection_subject_ownership.sql +53 -0
  35. package/package.json +3 -3
  36. package/src/codex-token-resolver.ts +102 -49
  37. package/src/connection-token-resolver.ts +67 -26
  38. package/src/database.ts +393 -0
  39. package/src/index.ts +439 -516
  40. package/src/insights.ts +2 -2
  41. package/src/memory-governance.ts +2 -2
  42. package/src/new-session-drafts.ts +1 -1
  43. package/src/preference-registry.ts +2 -2
  44. package/src/provision-roles.ts +1 -1
  45. package/src/runtime-posture.ts +1 -1
  46. package/src/schema.ts +16 -3
  47. package/src/scoped-knowledge.ts +2 -2
  48. package/src/session-control.ts +12 -5
  49. package/src/session-queue-commands.ts +4 -1
  50. package/src/session-realtime-context.ts +1 -1
  51. package/src/session-realtime-ledger.ts +1 -1
  52. package/src/session-realtime-mirror.ts +1 -1
  53. package/src/session-realtime-state.ts +1 -1
  54. package/src/session-realtime-terminal.ts +1 -1
  55. package/src/session-realtime.ts +1 -1
  56. package/src/session-tool-call-settlement.ts +1 -1
  57. package/src/turn-initiator.ts +1 -1
  58. package/src/workspace-artifacts.ts +2 -2
  59. package/src/workspace-instruction-policies.ts +2 -2
  60. package/dist/chunk-EX7CDSGH.js.map +0 -1
  61. package/dist/chunk-IHPCI4GV.js.map +0 -1
@@ -3,7 +3,7 @@
3
3
  // Hoisted here from apps/worker/src/activities/codex-auth.ts so BOTH the worker
4
4
  // (turn-time bearer for the streamed run) AND the api (the /wham/usage quota-bar
5
5
  // reads) drive ONE resolver — no duplicated refresh/CAS/single-flight logic. The
6
- // worker re-exports buildCodexTokenResolver from this module for back-compat, so
6
+ // worker re-exports buildCodexTokenResolver from @opengeni/db for back-compat, so
7
7
  // the agent-turn.ts call site is unchanged.
8
8
  //
9
9
  // Why @opengeni/db is the right home: the resolver only orchestrates accessors
@@ -35,15 +35,52 @@ import {
35
35
  refreshCodexToken,
36
36
  } from "@opengeni/codex";
37
37
  import { encryptEnvironmentValue } from "./environment-crypto";
38
- import {
39
- loadCodexCredentialForRun,
40
- recordCodexAccountUsage,
41
- recordCodexTokenRefresh,
42
- setCodexCredentialStatus,
43
- withCodexCredentialRefreshLock,
44
- type CodexCredentialForRun,
45
- type Database,
46
- } from "./index";
38
+ import type { Database } from "./database";
39
+
40
+ export type CodexCredentialTokens = {
41
+ accessToken: string;
42
+ refreshToken: string;
43
+ idToken: string;
44
+ };
45
+
46
+ export type CodexCredentialForRun = {
47
+ id: string;
48
+ version: number;
49
+ workspaceId: string;
50
+ tokens: CodexCredentialTokens;
51
+ chatgptAccountId: string | null;
52
+ scopes: string | null;
53
+ planType: string | null;
54
+ isFedramp: boolean;
55
+ expiresAt: Date | null;
56
+ lastRefreshAt: Date | null;
57
+ status: string;
58
+ lastError: string | null;
59
+ };
60
+
61
+ export type CodexAccountUsageSnapshot = {
62
+ primaryUsedPercent?: number | null;
63
+ primaryResetAt?: Date | null;
64
+ secondaryUsedPercent?: number | null;
65
+ secondaryResetAt?: Date | null;
66
+ checkedAt?: Date;
67
+ resetCreditAvailableCount?: number | null;
68
+ resetCreditsCheckedAt?: Date | null;
69
+ };
70
+
71
+ type CodexCredentialRefreshInput = {
72
+ id: string;
73
+ version: number;
74
+ workspaceId: string;
75
+ credentialEncrypted: string;
76
+ expiresAt: Date | null;
77
+ lastRefreshAt: Date;
78
+ };
79
+
80
+ type CodexCredentialStatusTarget = {
81
+ id: string;
82
+ version: number;
83
+ };
47
84
 
48
85
  // Single-flight per CREDENTIAL INSTANCE (row id + version), process-module scope.
49
86
  // Keying by the loaded credential's id+version — NOT by workspaceId alone (P1-b) —
@@ -142,25 +179,37 @@ export async function withCodexTokenDeadline<T>(
142
179
 
143
180
  // Dependencies are injectable so the lifecycle logic (single-flight, staleness,
144
181
  // needs_relogin transition) is unit-testable without a database. Production uses
145
- // the real db + codex functions via the default bag.
182
+ // the root composition wrapper supplies the real db + codex functions.
146
183
  export type CodexAuthDeps = {
147
- loadCredential: typeof loadCodexCredentialForRun;
148
- recordRefresh: typeof recordCodexTokenRefresh;
149
- setStatus: typeof setCodexCredentialStatus;
184
+ loadCredential: (
185
+ db: Database,
186
+ settings: Settings,
187
+ workspaceId: string,
188
+ credentialId: string,
189
+ ) => Promise<CodexCredentialForRun | null>;
190
+ recordRefresh: (db: Database, input: CodexCredentialRefreshInput) => Promise<boolean>;
191
+ setStatus: (
192
+ db: Database,
193
+ workspaceId: string,
194
+ status: "active" | "needs_relogin" | "error",
195
+ lastError: string | null,
196
+ target: CodexCredentialStatusTarget,
197
+ ) => Promise<boolean>;
150
198
  refresh: typeof refreshCodexToken;
151
199
  encrypt: typeof encryptEnvironmentValue;
152
200
  keyBytes: typeof environmentsEncryptionKeyBytes;
153
- withRefreshLock: typeof withCodexCredentialRefreshLock;
154
- };
155
-
156
- const defaultDeps: CodexAuthDeps = {
157
- loadCredential: loadCodexCredentialForRun,
158
- recordRefresh: recordCodexTokenRefresh,
159
- setStatus: setCodexCredentialStatus,
160
- refresh: refreshCodexToken,
161
- encrypt: encryptEnvironmentValue,
162
- keyBytes: environmentsEncryptionKeyBytes,
163
- withRefreshLock: withCodexCredentialRefreshLock,
201
+ withRefreshLock: <T>(
202
+ db: Database,
203
+ workspaceId: string,
204
+ credentialId: string,
205
+ fn: (lockedDb: Database) => Promise<T>,
206
+ ) => Promise<T>;
207
+ recordUsage?: (
208
+ db: Database,
209
+ workspaceId: string,
210
+ credentialId: string,
211
+ snapshot: CodexAccountUsageSnapshot,
212
+ ) => Promise<boolean>;
164
213
  };
165
214
 
166
215
  export function buildCodexTokenResolver(
@@ -173,7 +222,7 @@ export function buildCodexTokenResolver(
173
222
  // 0 rows against the now-inactive row — so a refresh racing a switch can never
174
223
  // clobber the newly-active account. The single-flight map needs zero change.
175
224
  credentialId: string,
176
- deps: CodexAuthDeps = defaultDeps,
225
+ deps: CodexAuthDeps,
177
226
  ): { getToken: () => Promise<CodexTokenSnapshot>; refresh: () => Promise<CodexTokenSnapshot> } {
178
227
  const snapshot = (cred: CodexCredentialForRun): CodexTokenSnapshot => ({
179
228
  accessToken: cred.tokens.accessToken,
@@ -336,9 +385,10 @@ export async function fetchCodexUsageForAccount(
336
385
  settings: Settings,
337
386
  workspaceId: string,
338
387
  credentialId: string,
388
+ deps: CodexAuthDeps,
339
389
  fetchImpl: CodexFetch = fetch,
340
390
  ): Promise<CodexUsagePayload> {
341
- const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
391
+ const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId, deps);
342
392
  let token: CodexTokenSnapshot;
343
393
  try {
344
394
  token = await resolver.getToken();
@@ -373,27 +423,29 @@ export async function fetchCodexUsageForAccount(
373
423
  // cached without erasing or falsely refreshing the last valid quota truth.
374
424
  // Cache-write is best-effort: a disconnect under us (false) or a transient
375
425
  // write error must NOT sink the freshly-read result we are about to return.
376
- await recordCodexAccountUsage(db, workspaceId, credentialId, {
377
- ...(parsedQuota
378
- ? {
379
- primaryUsedPercent: normalized.fiveHour?.percent ?? null,
380
- primaryResetAt: normalized.fiveHour?.resetAt
381
- ? new Date(normalized.fiveHour.resetAt)
382
- : null,
383
- secondaryUsedPercent: normalized.weekly?.percent ?? null,
384
- secondaryResetAt: normalized.weekly?.resetAt
385
- ? new Date(normalized.weekly.resetAt)
386
- : null,
387
- checkedAt,
388
- }
389
- : {}),
390
- ...(normalized.rateLimitResetCredits
391
- ? {
392
- resetCreditAvailableCount: normalized.rateLimitResetCredits.availableCount,
393
- resetCreditsCheckedAt: checkedAt,
394
- }
395
- : {}),
396
- }).catch(() => undefined);
426
+ await deps
427
+ .recordUsage?.(db, workspaceId, credentialId, {
428
+ ...(parsedQuota
429
+ ? {
430
+ primaryUsedPercent: normalized.fiveHour?.percent ?? null,
431
+ primaryResetAt: normalized.fiveHour?.resetAt
432
+ ? new Date(normalized.fiveHour.resetAt)
433
+ : null,
434
+ secondaryUsedPercent: normalized.weekly?.percent ?? null,
435
+ secondaryResetAt: normalized.weekly?.resetAt
436
+ ? new Date(normalized.weekly.resetAt)
437
+ : null,
438
+ checkedAt,
439
+ }
440
+ : {}),
441
+ ...(normalized.rateLimitResetCredits
442
+ ? {
443
+ resetCreditAvailableCount: normalized.rateLimitResetCredits.availableCount,
444
+ resetCreditsCheckedAt: checkedAt,
445
+ }
446
+ : {}),
447
+ })
448
+ .catch(() => undefined);
397
449
  }
398
450
 
399
451
  return normalized;
@@ -418,9 +470,10 @@ export async function fetchCodexRateLimitResetCreditsForAccount(
418
470
  settings: Settings,
419
471
  workspaceId: string,
420
472
  credentialId: string,
473
+ deps: CodexAuthDeps,
421
474
  fetchImpl: CodexFetch = fetch,
422
475
  ): Promise<CodexRateLimitResetCreditsAccountResult> {
423
- const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
476
+ const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId, deps);
424
477
  let token: CodexTokenSnapshot;
425
478
  try {
426
479
  token = await resolver.getToken();
@@ -4,7 +4,9 @@ import {
4
4
  type Settings,
5
5
  } from "@opengeni/config";
6
6
  import type {
7
+ ConnectionKind,
7
8
  ConnectionCredentialsPort,
9
+ ConnectionStatus,
8
10
  McpConnectionResourceScope,
9
11
  McpCredentialAuthNeededReason,
10
12
  McpCredentialsRequest,
@@ -24,14 +26,49 @@ export { isPrivateAddress } from "@opengeni/network";
24
26
  import { Buffer } from "node:buffer";
25
27
  import { isIP } from "node:net";
26
28
  import { encryptEnvironmentValue } from "./environment-crypto";
27
- import {
28
- loadConnectionCredentialForBroker,
29
- recordConnectionTokenRefresh,
30
- recordConnectionUsed,
31
- setConnectionStatus,
32
- type ConnectionCredentialForBroker,
33
- type Database,
34
- } from "./index";
29
+ import type { Database } from "./database";
30
+
31
+ export type ConnectionCredentialForBroker = {
32
+ id: string;
33
+ accountId: string;
34
+ workspaceId: string;
35
+ subjectId: string | null;
36
+ providerDomain: string;
37
+ kind: ConnectionKind;
38
+ status: ConnectionStatus;
39
+ credential: Record<string, unknown>;
40
+ grantedScopes: string[];
41
+ expiresAt: Date | null;
42
+ lastRefreshAt: Date | null;
43
+ version: number;
44
+ metadata: Record<string, unknown>;
45
+ };
46
+
47
+ export type ConnectionCredentialLookupInput = {
48
+ workspaceId: string;
49
+ connectionId?: string;
50
+ providerDomain: string;
51
+ kind?: ConnectionKind;
52
+ subjectId?: string | null;
53
+ allowSubjectOwned?: boolean;
54
+ };
55
+
56
+ export type ConnectionTokenRefreshInput = {
57
+ id: string;
58
+ version: number;
59
+ workspaceId: string;
60
+ credentialEncrypted: string;
61
+ expiresAt: Date | null;
62
+ grantedScopes?: string[];
63
+ lastRefreshAt: Date;
64
+ subjectId?: string | null;
65
+ };
66
+
67
+ export type ConnectionStatusGuard = {
68
+ id: string;
69
+ version: number;
70
+ subjectId?: string | null;
71
+ };
35
72
 
36
73
  export type ResolveConnectionCredentialResult =
37
74
  | {
@@ -334,10 +371,25 @@ function parseHostCredentialExpiry(value: string | null | undefined): Date | nul
334
371
  }
335
372
 
336
373
  export type ConnectionBrokerDeps = {
337
- loadCredential: typeof loadConnectionCredentialForBroker;
338
- recordRefresh: typeof recordConnectionTokenRefresh;
339
- setStatus: typeof setConnectionStatus;
340
- recordUsed: typeof recordConnectionUsed;
374
+ loadCredential: (
375
+ db: Database,
376
+ settings: Settings,
377
+ input: ConnectionCredentialLookupInput,
378
+ ) => Promise<ConnectionCredentialForBroker | null>;
379
+ recordRefresh: (db: Database, input: ConnectionTokenRefreshInput) => Promise<boolean>;
380
+ setStatus: (
381
+ db: Database,
382
+ workspaceId: string,
383
+ status: ConnectionStatus,
384
+ lastError: string | null,
385
+ guard: ConnectionStatusGuard,
386
+ ) => Promise<boolean>;
387
+ recordUsed: (
388
+ db: Database,
389
+ workspaceId: string,
390
+ connectionId: string,
391
+ subjectId?: string | null,
392
+ ) => Promise<void>;
341
393
  refresh: typeof refreshOAuthConnectionCredential;
342
394
  encrypt: typeof encryptEnvironmentValue;
343
395
  keyBytes: typeof environmentsEncryptionKeyBytes;
@@ -372,17 +424,6 @@ export type RefreshTransportOptions = {
372
424
  dnsLookup?: DnsLookup;
373
425
  };
374
426
 
375
- const defaultDeps: ConnectionBrokerDeps = {
376
- loadCredential: loadConnectionCredentialForBroker,
377
- recordRefresh: recordConnectionTokenRefresh,
378
- setStatus: setConnectionStatus,
379
- recordUsed: recordConnectionUsed,
380
- refresh: refreshOAuthConnectionCredential,
381
- encrypt: encryptEnvironmentValue,
382
- keyBytes: environmentsEncryptionKeyBytes,
383
- now: () => new Date(),
384
- };
385
-
386
427
  const inflight = new Map<string, Promise<ConnectionCredentialForBroker>>();
387
428
  const REFRESH_WINDOW_MS = 60_000;
388
429
  const CONNECTION_REFRESH_TIMEOUT_MS = 10_000;
@@ -390,7 +431,7 @@ const CONNECTION_REFRESH_TIMEOUT_MS = 10_000;
390
431
  export function buildConnectionTokenResolver(
391
432
  db: Database,
392
433
  settings: Settings,
393
- deps: ConnectionBrokerDeps = defaultDeps,
434
+ deps: ConnectionBrokerDeps,
394
435
  options: ConnectionTokenResolverOptions = {},
395
436
  ): (input: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
396
437
  type CredentialLookupInput = Pick<
@@ -404,7 +445,7 @@ export function buildConnectionTokenResolver(
404
445
  if (subjectOwned && !input.subjectId) {
405
446
  return null;
406
447
  }
407
- const request: Parameters<typeof loadConnectionCredentialForBroker>[2] = {
448
+ const request: ConnectionCredentialLookupInput = {
408
449
  workspaceId: input.workspaceId,
409
450
  providerDomain: input.connectionRef.providerDomain,
410
451
  allowSubjectOwned: subjectOwned,
@@ -484,7 +525,7 @@ export function buildConnectionTokenResolver(
484
525
  throw new Error("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
485
526
  }
486
527
  const refreshed = await deps.refresh(cred, ref, settings, options.refreshTransport);
487
- const refreshRecord: Parameters<typeof recordConnectionTokenRefresh>[1] = {
528
+ const refreshRecord: ConnectionTokenRefreshInput = {
488
529
  id: cred.id,
489
530
  version: cred.version,
490
531
  workspaceId: cred.workspaceId,