@opengeni/db 0.23.0 → 0.27.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.
- package/dist/{chunk-3UCHDMKG.js → chunk-EX7CDSGH.js} +196 -2
- package/dist/chunk-EX7CDSGH.js.map +1 -0
- package/dist/{chunk-L6ADMZHE.js → chunk-IHPCI4GV.js} +5 -1
- package/dist/chunk-IHPCI4GV.js.map +1 -0
- package/dist/connection-token-resolver.d.ts +24 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +1457 -338
- package/dist/index.js.map +1 -1
- package/dist/preference-registry.d.ts +14 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +3 -3
- package/dist/schema.d.ts +262 -4
- package/dist/schema.js +5 -1
- package/dist/session-realtime-mirror.d.ts +22 -1
- package/dist/workspace-instruction-policies-schema.d.ts +449 -0
- package/dist/workspace-instruction-policies.d.ts +88 -8
- package/drizzle/0165_document_authority_foundation.sql +259 -0
- package/drizzle/0166_connection_disconnect_idempotency.sql +49 -0
- package/drizzle/0167_document_index_replay_authority.sql +61 -0
- package/drizzle/0168_workspace_instruction_policy_operation_receipts.sql +44 -0
- package/drizzle/0169_workspace_instruction_policy_onboarding_proposals.sql +202 -0
- package/package.json +3 -3
- package/src/connection-token-resolver.ts +79 -16
- package/src/index.ts +419 -23
- package/src/preference-registry.ts +103 -0
- package/src/runtime-posture.ts +4 -0
- package/src/schema.ts +86 -0
- package/src/session-control.ts +48 -1
- package/src/session-queue-commands.ts +45 -11
- package/src/session-realtime-mirror.ts +117 -1
- package/src/session-realtime.ts +49 -1
- package/src/workspace-instruction-policies-schema.ts +116 -0
- package/src/workspace-instruction-policies.ts +819 -25
- package/dist/chunk-3UCHDMKG.js.map +0 -1
- package/dist/chunk-L6ADMZHE.js.map +0 -1
|
@@ -34,7 +34,14 @@ import {
|
|
|
34
34
|
} from "./index";
|
|
35
35
|
|
|
36
36
|
export type ResolveConnectionCredentialResult =
|
|
37
|
-
| {
|
|
37
|
+
| {
|
|
38
|
+
status: "ok";
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
connectionId: string;
|
|
41
|
+
/** Exact durable version when the credential came from the local connection store. */
|
|
42
|
+
connectionVersion?: number;
|
|
43
|
+
expiresAt?: Date | null;
|
|
44
|
+
}
|
|
38
45
|
| {
|
|
39
46
|
status: "auth_needed";
|
|
40
47
|
reason: McpCredentialAuthNeededReason;
|
|
@@ -337,6 +344,29 @@ export type ConnectionBrokerDeps = {
|
|
|
337
344
|
now: () => Date;
|
|
338
345
|
};
|
|
339
346
|
|
|
347
|
+
export type PermanentConnectionRefreshFailure = {
|
|
348
|
+
workspaceId: string;
|
|
349
|
+
connectionId: string;
|
|
350
|
+
connectionVersion: number;
|
|
351
|
+
subjectId: string | null;
|
|
352
|
+
providerDomain: string;
|
|
353
|
+
httpStatus: number;
|
|
354
|
+
oauthErrorCode: string | null;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export type ConnectionTokenResolverOptions = {
|
|
358
|
+
/** Provider-aware transport override used by local/integration adapters. */
|
|
359
|
+
refreshTransport?: RefreshTransportOptions;
|
|
360
|
+
/**
|
|
361
|
+
* Optional provider adapter for an atomic, metadata-aware permanent refresh
|
|
362
|
+
* transition. Returning true means the adapter owned the transition (even if
|
|
363
|
+
* its CAS lost to newer truth); false falls back to generic needs_reauth.
|
|
364
|
+
*/
|
|
365
|
+
transitionPermanentRefreshFailure?: (
|
|
366
|
+
failure: PermanentConnectionRefreshFailure,
|
|
367
|
+
) => Promise<boolean>;
|
|
368
|
+
};
|
|
369
|
+
|
|
340
370
|
export type RefreshTransportOptions = {
|
|
341
371
|
fetchImpl?: FetchLike;
|
|
342
372
|
dnsLookup?: DnsLookup;
|
|
@@ -361,6 +391,7 @@ export function buildConnectionTokenResolver(
|
|
|
361
391
|
db: Database,
|
|
362
392
|
settings: Settings,
|
|
363
393
|
deps: ConnectionBrokerDeps = defaultDeps,
|
|
394
|
+
options: ConnectionTokenResolverOptions = {},
|
|
364
395
|
): (input: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
|
|
365
396
|
type CredentialLookupInput = Pick<
|
|
366
397
|
ResolveConnectionCredentialInput,
|
|
@@ -439,6 +470,7 @@ export function buildConnectionTokenResolver(
|
|
|
439
470
|
status: "ok",
|
|
440
471
|
headers,
|
|
441
472
|
connectionId: cred.id,
|
|
473
|
+
connectionVersion: cred.version,
|
|
442
474
|
expiresAt: cred.expiresAt,
|
|
443
475
|
};
|
|
444
476
|
};
|
|
@@ -451,7 +483,7 @@ export function buildConnectionTokenResolver(
|
|
|
451
483
|
if (!key) {
|
|
452
484
|
throw new Error("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured");
|
|
453
485
|
}
|
|
454
|
-
const refreshed = await deps.refresh(cred, ref, settings);
|
|
486
|
+
const refreshed = await deps.refresh(cred, ref, settings, options.refreshTransport);
|
|
455
487
|
const refreshRecord: Parameters<typeof recordConnectionTokenRefresh>[1] = {
|
|
456
488
|
id: cred.id,
|
|
457
489
|
version: cred.version,
|
|
@@ -541,19 +573,31 @@ export function buildConnectionTokenResolver(
|
|
|
541
573
|
// Only a rejected grant may poison the connection; transient failures
|
|
542
574
|
// (network errors, AS 5xx) leave it active so the next resolve retries.
|
|
543
575
|
if (isPermanentRefreshError(error)) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
576
|
+
let handled = false;
|
|
577
|
+
if (options.transitionPermanentRefreshFailure) {
|
|
578
|
+
try {
|
|
579
|
+
handled = await options.transitionPermanentRefreshFailure({
|
|
580
|
+
workspaceId: cred.workspaceId,
|
|
581
|
+
connectionId: cred.id,
|
|
582
|
+
connectionVersion: cred.version,
|
|
583
|
+
subjectId: cred.subjectId,
|
|
584
|
+
providerDomain: cred.providerDomain,
|
|
585
|
+
httpStatus: error.httpStatus,
|
|
586
|
+
oauthErrorCode: error.oauthErrorCode,
|
|
587
|
+
});
|
|
588
|
+
} catch {
|
|
589
|
+
handled = false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!handled) {
|
|
593
|
+
await deps
|
|
594
|
+
.setStatus(db, input.workspaceId, "needs_reauth", error.message, {
|
|
551
595
|
id: cred.id,
|
|
552
596
|
version: cred.version,
|
|
553
597
|
subjectId: cred.subjectId,
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
598
|
+
})
|
|
599
|
+
.catch(() => undefined);
|
|
600
|
+
}
|
|
557
601
|
}
|
|
558
602
|
return authNeeded(ref, "refresh_failed", cred.id);
|
|
559
603
|
}
|
|
@@ -629,18 +673,20 @@ function canonicalResource(value: string): string {
|
|
|
629
673
|
|
|
630
674
|
export class ConnectionRefreshHttpError extends Error {
|
|
631
675
|
readonly httpStatus: number;
|
|
676
|
+
readonly oauthErrorCode: string | null;
|
|
632
677
|
|
|
633
|
-
constructor(httpStatus: number) {
|
|
678
|
+
constructor(httpStatus: number, oauthErrorCode: string | null = null) {
|
|
634
679
|
super(`connection refresh failed with HTTP ${httpStatus}`);
|
|
635
680
|
this.name = "ConnectionRefreshHttpError";
|
|
636
681
|
this.httpStatus = httpStatus;
|
|
682
|
+
this.oauthErrorCode = oauthErrorCode;
|
|
637
683
|
}
|
|
638
684
|
}
|
|
639
685
|
|
|
640
686
|
// The token endpoint rejecting the grant itself means re-auth is the only way
|
|
641
687
|
// forward. 429 (throttling) and 408 are transient despite being 4xx; network
|
|
642
688
|
// failures and AS 5xx are likewise retryable.
|
|
643
|
-
function isPermanentRefreshError(error: unknown):
|
|
689
|
+
function isPermanentRefreshError(error: unknown): error is ConnectionRefreshHttpError {
|
|
644
690
|
return (
|
|
645
691
|
error instanceof ConnectionRefreshHttpError &&
|
|
646
692
|
error.httpStatus >= 400 &&
|
|
@@ -817,8 +863,10 @@ export async function refreshOAuthConnectionCredential(
|
|
|
817
863
|
throw new ConnectionRefreshHttpError(response.status);
|
|
818
864
|
}
|
|
819
865
|
if (!response.ok) {
|
|
820
|
-
|
|
821
|
-
|
|
866
|
+
throw new ConnectionRefreshHttpError(
|
|
867
|
+
response.status,
|
|
868
|
+
await readOAuthRefreshErrorCode(response),
|
|
869
|
+
);
|
|
822
870
|
}
|
|
823
871
|
const payload = await readResponseJsonBounded<Record<string, unknown>>(
|
|
824
872
|
response,
|
|
@@ -853,6 +901,21 @@ export async function refreshOAuthConnectionCredential(
|
|
|
853
901
|
};
|
|
854
902
|
}
|
|
855
903
|
|
|
904
|
+
async function readOAuthRefreshErrorCode(response: Response): Promise<string | null> {
|
|
905
|
+
try {
|
|
906
|
+
const payload = await readResponseJsonBounded<Record<string, unknown>>(
|
|
907
|
+
response,
|
|
908
|
+
OAUTH_MAX_RESPONSE_BYTES,
|
|
909
|
+
"OAuth refresh error response",
|
|
910
|
+
);
|
|
911
|
+
const code = stringValue(payload.error);
|
|
912
|
+
return code && /^[a-z0-9_.-]{1,64}$/i.test(code) ? code : null;
|
|
913
|
+
} catch {
|
|
914
|
+
await cancelResponseBody(response);
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
856
919
|
function expiresAtFromTokenResponse(
|
|
857
920
|
payload: Record<string, unknown>,
|
|
858
921
|
fallback: Date | null,
|