@opengeni/db 4.4.0-canary.0 → 4.4.0-canary.2

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 (105) hide show
  1. package/dist/canonical-human-identities.js +4 -3
  2. package/dist/{chunk-2ZMTQVTR.js → chunk-2KOLL33E.js} +2 -2
  3. package/dist/chunk-45CP2YKY.js +1062 -0
  4. package/dist/chunk-45CP2YKY.js.map +1 -0
  5. package/dist/{chunk-6TKL3GQO.js → chunk-5UUWZDAV.js} +6531 -6480
  6. package/dist/chunk-5UUWZDAV.js.map +1 -0
  7. package/dist/{chunk-QLA3UQNX.js → chunk-6IJD3XXF.js} +17 -3
  8. package/dist/chunk-6IJD3XXF.js.map +1 -0
  9. package/dist/{chunk-232VC2LW.js → chunk-6V3HDEMR.js} +2 -2
  10. package/dist/{chunk-M7PSMVFF.js → chunk-CHBLBEU6.js} +2 -2
  11. package/dist/chunk-M4PKBJRS.js +20318 -0
  12. package/dist/chunk-M4PKBJRS.js.map +1 -0
  13. package/dist/chunk-S2TZ3F4L.js +237 -0
  14. package/dist/chunk-S2TZ3F4L.js.map +1 -0
  15. package/dist/{chunk-UK57QA2H.js → chunk-UOXWVP3T.js} +2 -2
  16. package/dist/{chunk-GVZJ7XSR.js → chunk-WFU6PS54.js} +11 -4
  17. package/dist/chunk-WFU6PS54.js.map +1 -0
  18. package/dist/{chunk-THJGMEK5.js → chunk-XJP7KJ6U.js} +121 -66
  19. package/dist/chunk-XJP7KJ6U.js.map +1 -0
  20. package/dist/connection-token-resolver.d.ts +2 -0
  21. package/dist/database.d.ts +2 -0
  22. package/dist/editable-artifact-durable-export.js +3 -2
  23. package/dist/editable-artifact-durable-export.js.map +1 -1
  24. package/dist/editable-artifacts.js +4 -3
  25. package/dist/external-identities.d.ts +1 -0
  26. package/dist/external-membership-operations.d.ts +44 -0
  27. package/dist/governed-learning-evaluator.d.ts +1 -14
  28. package/dist/host-mcp-bindings.d.ts +4 -0
  29. package/dist/host-mcp-resolvers.d.ts +47 -0
  30. package/dist/host-mcp-task-authority.d.ts +2 -6
  31. package/dist/index.d.ts +29 -31
  32. package/dist/index.js +8616 -8321
  33. package/dist/index.js.map +1 -1
  34. package/dist/knowledge-document-preparation.d.ts +56 -0
  35. package/dist/knowledge-entries-schema.d.ts +2479 -0
  36. package/dist/knowledge-entries.d.ts +421 -0
  37. package/dist/knowledge-indexing.d.ts +99 -0
  38. package/dist/knowledge-migration.d.ts +6 -0
  39. package/dist/managed-auth-session-sets.js +4 -3
  40. package/dist/mcp-operations.js +3 -2
  41. package/dist/mcp-operations.js.map +1 -1
  42. package/dist/migrate.js +2 -1
  43. package/dist/provision-roles.js +1 -1
  44. package/dist/retained-provider-commands.js +3 -2
  45. package/dist/retained-provider-commands.js.map +1 -1
  46. package/dist/runtime-posture.d.ts +7 -7
  47. package/dist/schema.d.ts +115 -12
  48. package/dist/schema.js +26 -1
  49. package/dist/session-background-commands.js +5 -4
  50. package/dist/session-command-output.js +8 -6
  51. package/dist/session-command-output.js.map +1 -1
  52. package/dist/session-event-slices.js +6 -4
  53. package/dist/session-event-slices.js.map +1 -1
  54. package/dist/session-mcp-credential-rotation.js +5 -4
  55. package/dist/session-mcp-credential-rotation.js.map +1 -1
  56. package/dist/session-tenancy.js +4 -3
  57. package/dist/video-generation.js +4 -3
  58. package/dist/workspace-artifacts.d.ts +1 -0
  59. package/dist/workspace-instruction-policies-schema.d.ts +21 -2
  60. package/dist/workspace-instruction-policies.d.ts +1 -1
  61. package/dist/workspace-membership-access.d.ts +11 -0
  62. package/dist/workspace-tool-defaults.js +5 -4
  63. package/dist/workspace-tool-defaults.js.map +1 -1
  64. package/drizzle/0461_unified_knowledge.sql +2859 -0
  65. package/drizzle/0462_agent_instruction_non_destructive_edits.sql +212 -0
  66. package/drizzle/0463_host_mcp_resolver_registration.sql +111 -0
  67. package/drizzle/0464_external_workspace_membership_operations.sql +166 -0
  68. package/package.json +6 -6
  69. package/src/connection-token-resolver.ts +75 -6
  70. package/src/database.ts +12 -1
  71. package/src/external-identities.ts +1 -0
  72. package/src/external-membership-operations.ts +134 -0
  73. package/src/governed-learning-evaluator.ts +0 -69
  74. package/src/host-mcp-binding-match.ts +1 -0
  75. package/src/host-mcp-bindings.ts +24 -1
  76. package/src/host-mcp-resolvers.ts +266 -0
  77. package/src/host-mcp-task-authority.ts +11 -10
  78. package/src/index.ts +307 -128
  79. package/src/knowledge-document-preparation.ts +90 -0
  80. package/src/knowledge-entries-schema.ts +264 -0
  81. package/src/knowledge-entries.ts +860 -0
  82. package/src/knowledge-indexing.ts +126 -0
  83. package/src/knowledge-migration.ts +761 -0
  84. package/src/knowledge-source-sync.ts +46 -7
  85. package/src/migrate.ts +19 -0
  86. package/src/provision-roles.ts +23 -57
  87. package/src/runtime-posture.ts +103 -7
  88. package/src/schema.ts +15 -3
  89. package/src/skills.ts +4 -0
  90. package/src/video-generation.ts +14 -0
  91. package/src/workspace-artifacts.ts +2 -0
  92. package/src/workspace-instruction-policies-schema.ts +1 -0
  93. package/src/workspace-membership-access.ts +61 -0
  94. package/dist/chunk-6TKL3GQO.js.map +0 -1
  95. package/dist/chunk-EYF6MH6C.js +0 -4772
  96. package/dist/chunk-EYF6MH6C.js.map +0 -1
  97. package/dist/chunk-GSBONN55.js +0 -533
  98. package/dist/chunk-GSBONN55.js.map +0 -1
  99. package/dist/chunk-GVZJ7XSR.js.map +0 -1
  100. package/dist/chunk-QLA3UQNX.js.map +0 -1
  101. package/dist/chunk-THJGMEK5.js.map +0 -1
  102. /package/dist/{chunk-2ZMTQVTR.js.map → chunk-2KOLL33E.js.map} +0 -0
  103. /package/dist/{chunk-232VC2LW.js.map → chunk-6V3HDEMR.js.map} +0 -0
  104. /package/dist/{chunk-M7PSMVFF.js.map → chunk-CHBLBEU6.js.map} +0 -0
  105. /package/dist/{chunk-UK57QA2H.js.map → chunk-UOXWVP3T.js.map} +0 -0
@@ -17,6 +17,7 @@ import type {
17
17
  TurnInitiator,
18
18
  TurnInitiatorContext,
19
19
  } from "@opengeni/contracts";
20
+ import { hostMcpCredentialUseGuard } from "@opengeni/contracts";
20
21
  import {
21
22
  ConnectionUseAuthoritySnapshot,
22
23
  type ConnectionUseAttribution,
@@ -48,6 +49,7 @@ import type {
48
49
  AcceptedConnectionUseResolution,
49
50
  } from "./connection-authority";
50
51
  import { connectionScopeKey } from "./connection-scopes";
52
+ import { hostMcpBindingMatchesSelection } from "@opengeni/contracts/host-mcp-bindings";
51
53
 
52
54
  const MAX_CREDENTIAL_PLACEMENTS = 32;
53
55
  const MAX_CREDENTIAL_NAME_BYTES = 256;
@@ -193,6 +195,10 @@ export type HostMcpCredentialResolverContext = {
193
195
  /** Backend-owned live validator. Never populate from caller JSON or a host
194
196
  * credential response. Omission denies every explicit durable reference. */
195
197
  authorizeDurableBinding?: (request: McpCredentialsRequest) => Promise<boolean>;
198
+ /** Backend-only accepted-work lookup, not a host response or registry fallback. */
199
+ resolveAcceptedBinding?: (
200
+ request: McpCredentialsRequest,
201
+ ) => Promise<McpServerConnectionRef | null>;
196
202
  };
197
203
 
198
204
  export class HostMcpCredentialScopeError extends Error {
@@ -325,8 +331,8 @@ export function buildHostConnectionTokenResolver(
325
331
  ...(toolName ? { toolName } : {}),
326
332
  ...(input.subjectId ? { callerSubjectId: input.subjectId } : {}),
327
333
  };
328
- const snapshot = structuredClone(request);
329
- const requestedRef = structuredClone(input.connectionRef);
334
+ // Freeze configuration before any asynchronous authority lookup.
335
+ let requestedRef = structuredClone(request.connectionRef);
330
336
  const credentialTarget = input.credentialTarget ?? "mcp";
331
337
  const durableDenial = (
332
338
  reason: McpCredentialAuthNeededReason,
@@ -337,6 +343,32 @@ export function buildHostConnectionTokenResolver(
337
343
  authoritySource: "host",
338
344
  ...(requestedRef.connectionId ? { connectionId: requestedRef.connectionId } : {}),
339
345
  });
346
+ if (requestedRef.hostBinding && "selection" in requestedRef.hostBinding) {
347
+ if (!context.resolveAcceptedBinding || !context.authorizeDurableBinding)
348
+ return durableDenial("unsupported_auth");
349
+ try {
350
+ const concrete = await context.resolveAcceptedBinding(structuredClone(request));
351
+ if (!concrete?.hostBinding || "selection" in concrete.hostBinding)
352
+ return durableDenial("personal_authority_unavailable");
353
+ const { hostBinding, ...connectionRef } = concrete;
354
+ if (
355
+ !hostMcpBindingMatchesSelection(
356
+ {
357
+ id: hostBinding.bindingId,
358
+ generation: hostBinding.generation,
359
+ definition: { serverId: request.serverId, destinationUrl, connectionRef },
360
+ },
361
+ request,
362
+ )
363
+ )
364
+ return durableDenial("personal_authority_unavailable");
365
+ request.connectionRef = structuredClone(concrete);
366
+ requestedRef = structuredClone(concrete);
367
+ } catch {
368
+ return durableDenial("refresh_failed");
369
+ }
370
+ }
371
+ const snapshot = structuredClone(request);
340
372
  const authorized = async () => {
341
373
  try {
342
374
  if (
@@ -362,14 +394,27 @@ export function buildHostConnectionTokenResolver(
362
394
  if (denial) return durableDenial(denial);
363
395
  }
364
396
  const result = await resolve(structuredClone(snapshot));
397
+ const routeGuard = result.status === "ok" ? result[hostMcpCredentialUseGuard] : undefined;
398
+ const routeAuthorized = async () => {
399
+ try {
400
+ return routeGuard ? await routeGuard() : true;
401
+ } catch {
402
+ return false;
403
+ }
404
+ };
365
405
  if (requiresAuthorization) {
366
406
  const denial = await authorized();
367
407
  if (denial) return durableDenial(denial);
368
408
  }
409
+ if (routeGuard && !(await routeAuthorized())) return durableDenial("refresh_failed");
369
410
  assertHostMcpCredentialScope(result, context);
370
411
  const normalized = normalizeHostCredentialResolution(result, requestedRef, credentialTarget);
371
- return normalized.status === "ok" && requiresAuthorization
372
- ? { ...normalized, authorizeProviderRequest: async () => (await authorized()) === null }
412
+ return normalized.status === "ok" && (requiresAuthorization || routeGuard)
413
+ ? {
414
+ ...normalized,
415
+ authorizeProviderRequest: async () =>
416
+ (await authorized()) === null && (await routeAuthorized()),
417
+ }
373
418
  : normalized;
374
419
  };
375
420
  }
@@ -415,6 +460,14 @@ export function buildHostGatewayConnectionTokenResolver(
415
460
  await reauthorize();
416
461
  const result = await resolve(structuredClone(request));
417
462
  await reauthorize();
463
+ const routeGuard = result.status === "ok" ? result[hostMcpCredentialUseGuard] : undefined;
464
+ if (routeGuard) {
465
+ try {
466
+ if (!(await routeGuard())) return deny("refresh_failed");
467
+ } catch {
468
+ return deny("refresh_failed");
469
+ }
470
+ }
418
471
  for (const field of ["accountId", "workspaceId", "requestId"] as const) {
419
472
  if (result[field] !== request[field])
420
473
  throw new Error(`host gateway credential ${field} scope mismatch`);
@@ -430,7 +483,7 @@ export function buildHostGatewayConnectionTokenResolver(
430
483
  authorizeProviderRequest: async () => {
431
484
  try {
432
485
  await reauthorize();
433
- return true;
486
+ return routeGuard ? await routeGuard() : true;
434
487
  } catch {
435
488
  return false;
436
489
  }
@@ -1548,6 +1601,15 @@ export async function refreshOAuthConnectionCredential(
1548
1601
  const expiresAt = expiresAtFromTokenResponse(payload, cred.expiresAt);
1549
1602
  const scopeText = stringValue(payload.scope);
1550
1603
  const returnedScopes = scopeText?.split(/[\s,]+/).filter(Boolean) ?? [];
1604
+ // A successful Microsoft refresh proves offline access, although Microsoft
1605
+ // reports only access-token permissions in the response's scope field.
1606
+ if (
1607
+ scopeText &&
1608
+ ref.providerDomain.toLowerCase() === "graph.microsoft.com" &&
1609
+ !returnedScopes.some((scope) => scope.toLowerCase() === "offline_access")
1610
+ ) {
1611
+ returnedScopes.push("offline_access");
1612
+ }
1551
1613
  const refreshTokenExpiresIn =
1552
1614
  typeof payload.refresh_token_expires_in === "number"
1553
1615
  ? payload.refresh_token_expires_in
@@ -1574,7 +1636,14 @@ export async function refreshOAuthConnectionCredential(
1574
1636
  "Bearer",
1575
1637
  ...(expiresAt ? { expires_at: expiresAt.toISOString() } : {}),
1576
1638
  ...(resource ? { resource } : {}),
1577
- ...(scopeText ? { scope: scopeText } : {}),
1639
+ ...(scopeText
1640
+ ? {
1641
+ scope:
1642
+ ref.providerDomain.toLowerCase() === "graph.microsoft.com"
1643
+ ? returnedScopes.join(" ")
1644
+ : scopeText,
1645
+ }
1646
+ : {}),
1578
1647
  ...(refreshTokenExpiresAt ? { refresh_token_expires_at: refreshTokenExpiresAt } : {}),
1579
1648
  ...(clientSecret && !personalGitHub
1580
1649
  ? { client_secret: clientSecret, token_endpoint_auth_method: authMethod }
package/src/database.ts CHANGED
@@ -53,6 +53,8 @@ export type RlsContext = {
53
53
 
54
54
  export type SessionRlsActorContext = {
55
55
  subjectId: string;
56
+ /** Host-verified personal file ownership, never a request-supplied subject. */
57
+ privateFileOwnerSubjectId?: string | null;
56
58
  initiatingHumanSubjectId?: string | null;
57
59
  };
58
60
 
@@ -74,6 +76,15 @@ export async function withSessionRlsActorContext<T>(
74
76
  "withSessionRlsActorContext: initiatingHumanSubjectId must be null or non-empty",
75
77
  );
76
78
  }
79
+ if (
80
+ actor.privateFileOwnerSubjectId !== undefined &&
81
+ actor.privateFileOwnerSubjectId !== null &&
82
+ !actor.privateFileOwnerSubjectId.trim()
83
+ ) {
84
+ throw new Error(
85
+ "withSessionRlsActorContext: privateFileOwnerSubjectId must be null or non-empty",
86
+ );
87
+ }
77
88
  return await sessionRlsActorContext.run(actor, fn);
78
89
  }
79
90
 
@@ -389,7 +400,7 @@ export async function setRlsContext(db: Database, context: RlsContext): Promise<
389
400
  if (sessionActor) {
390
401
  await setSubjectRlsContext(db, sessionActor.subjectId);
391
402
  await db.execute(
392
- sql`select set_config(
403
+ sql`select set_config('opengeni.private_file_owner', ${sessionActor.privateFileOwnerSubjectId ?? ""}, true), set_config(
393
404
  'opengeni.initiating_human_subject_id',
394
405
  ${sessionActor.initiatingHumanSubjectId ?? ""},
395
406
  true
@@ -5,6 +5,7 @@ import {
5
5
  ExternalIdentityReference,
6
6
  } from "@opengeni/contracts/external-identities";
7
7
  import { rawRows, withAccountRls, type Database } from "./database";
8
+ export * from "./external-membership-operations";
8
9
 
9
10
  /** Pass an open transaction to retain the key lock through the protected write. */
10
11
  export async function lockActiveExternalOrganizationKey(
@@ -0,0 +1,134 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { z } from "zod";
3
+ import {
4
+ ExternalIdentity,
5
+ ExternalIdentityLookup,
6
+ CancelExternalWorkspaceMemberGrantResponse,
7
+ type AddExternalWorkspaceMemberRequest,
8
+ type CancelExternalWorkspaceMemberGrantRequest,
9
+ type ExternalIdentityReference,
10
+ } from "@opengeni/contracts/external-identities";
11
+ import {
12
+ rawRows,
13
+ withAccountRls,
14
+ withWorkspaceSubjectRls,
15
+ setSubjectRlsContext,
16
+ type Database,
17
+ } from "./database";
18
+ import { grantWorkspaceAccess, listWorkspaceMembers } from "./workspace-membership-access";
19
+ import { removeWorkspaceMember } from "./organization-membership-lifecycle";
20
+
21
+ type ServiceScope = { organizationId: string; actorSubjectId: string };
22
+
23
+ export async function lookupExternalIdentity(
24
+ db: Database,
25
+ scope: ServiceScope,
26
+ identity: ExternalIdentityReference,
27
+ ) {
28
+ return withAccountRls(db, scope.organizationId, async (tx) => {
29
+ await setSubjectRlsContext(tx, scope.actorSubjectId);
30
+ const [row] = await rawRows<{ result: unknown }>(
31
+ tx,
32
+ sql`select lookup_external_identity(
33
+ ${scope.organizationId}::uuid, ${scope.actorSubjectId}, ${identity.source}, ${identity.externalId}
34
+ ) as result`,
35
+ );
36
+ return ExternalIdentityLookup.parse(row?.result);
37
+ });
38
+ }
39
+
40
+ async function prepare(db: Database, command: Record<string, unknown>) {
41
+ const [row] = await rawRows<{ result: unknown }>(
42
+ db,
43
+ sql`select prepare_external_workspace_membership_operation(${JSON.stringify(command)}::jsonb) as result`,
44
+ );
45
+ return z
46
+ .object({
47
+ replay: z.boolean(),
48
+ result: z.unknown().optional(),
49
+ identity: z.unknown().optional(),
50
+ })
51
+ .parse(row?.result);
52
+ }
53
+
54
+ async function record(db: Database, command: Record<string, unknown>, result: unknown) {
55
+ await db.execute(sql`select record_external_workspace_membership_operation(
56
+ ${JSON.stringify(command)}::jsonb, ${JSON.stringify(result)}::jsonb)`);
57
+ }
58
+
59
+ /** One keyed grant. An operation replay never restores a removed/reduced row. */
60
+ export async function addExternalWorkspaceMemberOperation(
61
+ db: Database,
62
+ scope: ServiceScope & { workspaceId: string },
63
+ request: AddExternalWorkspaceMemberRequest & { operationId: string },
64
+ ) {
65
+ const command = {
66
+ ...scope,
67
+ action: "grant",
68
+ ...request,
69
+ permissions: [...new Set(request.permissions)].sort(),
70
+ };
71
+ return withWorkspaceSubjectRls(db, scope.workspaceId, scope.actorSubjectId, async (tx) => {
72
+ const prepared = await prepare(tx, command);
73
+ if (prepared.replay)
74
+ return z.object({ identity: ExternalIdentity }).parse(prepared.result).identity;
75
+ const identity = ExternalIdentity.parse(prepared.identity);
76
+ const existing = (await listWorkspaceMembers(tx, scope.workspaceId)).find(
77
+ (member) => member.subjectId === identity.subjectId,
78
+ );
79
+ if (
80
+ existing &&
81
+ (existing.permissions.length !== command.permissions.length ||
82
+ existing.permissions.some((permission) => !command.permissions.includes(permission)))
83
+ ) {
84
+ // SQLSTATE matches the native optimistic-concurrency contract.
85
+ await tx.execute(
86
+ sql`do $$ begin raise exception 'existing membership differs' using errcode = '40001'; end $$`,
87
+ );
88
+ }
89
+ if (!existing)
90
+ await grantWorkspaceAccess(tx, {
91
+ accountId: scope.organizationId,
92
+ workspaceId: scope.workspaceId,
93
+ subjectId: identity.subjectId,
94
+ role: "member",
95
+ permissions: command.permissions,
96
+ });
97
+ await record(tx, command, { workspaceId: scope.workspaceId, identity });
98
+ return identity;
99
+ });
100
+ }
101
+
102
+ /** The native removal owns all teardown; this wrapper adds an immutable causal
103
+ * fence even when an earlier grant has not reached the database yet. */
104
+ export async function cancelExternalWorkspaceMemberGrant(
105
+ db: Database,
106
+ scope: ServiceScope & { workspaceId: string; membershipId: string },
107
+ request: CancelExternalWorkspaceMemberGrantRequest,
108
+ ) {
109
+ const command = { ...scope, action: "revoke", ...request };
110
+ return withWorkspaceSubjectRls(db, scope.workspaceId, scope.actorSubjectId, async (tx) => {
111
+ const prepared = await prepare(tx, command);
112
+ if (prepared.replay) {
113
+ const stored = z
114
+ .object({ removed: z.boolean(), fencedGrantOperationId: z.string().uuid() })
115
+ .parse(prepared.result);
116
+ return CancelExternalWorkspaceMemberGrantResponse.parse({ ...stored, replay: true });
117
+ }
118
+ const identity = z.object({ subjectId: z.string() }).parse(prepared.identity);
119
+ const removed = await removeWorkspaceMember(tx, {
120
+ accountId: scope.organizationId,
121
+ workspaceId: scope.workspaceId,
122
+ actorSubjectId: scope.actorSubjectId,
123
+ targetSubjectId: identity.subjectId,
124
+ operationId: request.operationId,
125
+ });
126
+ const result = {
127
+ removed,
128
+ replay: false,
129
+ fencedGrantOperationId: request.cancelGrantOperationId,
130
+ };
131
+ await record(tx, command, { ...result, workspaceId: scope.workspaceId });
132
+ return result;
133
+ });
134
+ }
@@ -1,10 +1,6 @@
1
1
  import {
2
- EvaluateGovernedLearningProposalRequest,
3
2
  GovernedLearningDecisionReceipt,
4
- GovernedLearningEvaluationAttempt,
5
3
  type GovernedLearningDecisionReceipt as GovernedLearningDecisionReceiptType,
6
- type GovernedLearningEvaluationAttempt as GovernedLearningEvaluationAttemptType,
7
- type EvaluateGovernedLearningProposalRequest as EvaluateGovernedLearningProposalRequestType,
8
4
  } from "@opengeni/contracts";
9
5
  import { sql } from "drizzle-orm";
10
6
  import type { Database } from "./database";
@@ -15,15 +11,6 @@ export class GovernedLearningEvaluationAuthorityError extends Error {
15
11
  readonly name = "GovernedLearningEvaluationAuthorityError";
16
12
  }
17
13
 
18
- export class GovernedLearningEvaluationConflictError extends Error {
19
- readonly name = "GovernedLearningEvaluationConflictError";
20
- }
21
-
22
- export type GovernedLearningEvaluationInput = Readonly<{
23
- attempt: GovernedLearningEvaluationAttemptType;
24
- request: EvaluateGovernedLearningProposalRequestType;
25
- }>;
26
-
27
14
  type ReceiptRow = {
28
15
  receipt_id: string;
29
16
  operation_id: string;
@@ -159,59 +146,3 @@ export async function listGovernedLearningDecisionReceipts(
159
146
  throw error;
160
147
  }
161
148
  }
162
-
163
- /**
164
- * Persist one inert, content-free governed-learning decision. The database
165
- * capability independently revalidates the accepted attempt, policy snapshot,
166
- * proposal lineage, current evidence authority, and deterministic verdict.
167
- */
168
- export async function evaluateGovernedLearningProposal(
169
- db: Database,
170
- input: GovernedLearningEvaluationInput,
171
- ): Promise<GovernedLearningDecisionReceiptType> {
172
- const attempt = GovernedLearningEvaluationAttempt.parse(input.attempt);
173
- const request = EvaluateGovernedLearningProposalRequest.parse(input.request);
174
- try {
175
- return await withWorkspaceSubjectRls(
176
- db,
177
- attempt.workspaceId,
178
- attempt.subjectId,
179
- async (scopedDb) => {
180
- const rows = await rawRows<ReceiptRow>(
181
- scopedDb,
182
- sql`SELECT * FROM evaluate_governed_learning_proposal(
183
- current_setting('opengeni.account_id')::uuid,
184
- ${attempt.workspaceId}::uuid,
185
- ${attempt.sessionId}::uuid,
186
- ${attempt.turnId}::uuid,
187
- ${attempt.attemptId}::uuid,
188
- ${attempt.executionGeneration}::integer,
189
- ${request.operationId}::uuid,
190
- ${request.policySnapshotId}::uuid,
191
- ${request.proposalId}::uuid,
192
- ${request.claimId}::uuid,
193
- ${request.evidenceId}::uuid
194
- )`,
195
- );
196
- const row = rows[0];
197
- if (!row || rows.length !== 1) {
198
- throw new Error("Governed-learning evaluation returned no unique receipt");
199
- }
200
- return receiptFromRow(row);
201
- },
202
- );
203
- } catch (error) {
204
- const state = nestedPostgresSqlState(error);
205
- if (state === "42501") {
206
- throw new GovernedLearningEvaluationAuthorityError(
207
- "Governed-learning proposal evaluation is unavailable",
208
- );
209
- }
210
- if (state === "23505" || state === "40001") {
211
- throw new GovernedLearningEvaluationConflictError(
212
- "Governed-learning proposal evaluation conflicted",
213
- );
214
- }
215
- throw error;
216
- }
217
- }
@@ -14,6 +14,7 @@ export function hostMcpBindingMatchesRequest(
14
14
  const { hostBinding, ...connectionRef } = request.connectionRef;
15
15
  if (
16
16
  !hostBinding ||
17
+ "selection" in hostBinding ||
17
18
  binding.status !== "active" ||
18
19
  binding.revokedAt !== null ||
19
20
  binding.id !== hostBinding.bindingId ||
@@ -10,6 +10,7 @@ import {
10
10
  IssueHostMcpDelegationRequest,
11
11
  HostMcpAcceptedAuthority,
12
12
  HostMcpOwnerSubject,
13
+ hostMcpBindingMatchesSelection,
13
14
  } from "@opengeni/contracts/host-mcp-bindings";
14
15
  import { rawRows, withWorkspaceSubjectRls, type Database } from "./database";
15
16
  import { listSelfOrganizationMemberships } from "./organization-membership-lifecycle";
@@ -732,10 +733,14 @@ async function resolveDirectHostMcpUseAuthority(
732
733
  (delegation.grant.mode === "session" &&
733
734
  (delegation.grant.sessionId !== request.sessionId ||
734
735
  delegation.grant.expectedAuthorityEpoch !== a.targetSessionAuthorityEpoch)) ||
736
+ binding.status !== "active" ||
737
+ binding.revokedAt !== null ||
735
738
  binding.authorizationRevision !== a.ownerMembershipAuthorizationRevision ||
736
739
  binding.generation !== a.bindingGeneration ||
737
740
  stableJson(binding.definition) !== stableJson(a.definition) ||
738
- !hostMcpBindingMatchesRequest(binding, request)
741
+ !(request.connectionRef.hostBinding && "selection" in request.connectionRef.hostBinding
742
+ ? request.credentialTarget === "mcp" && hostMcpBindingMatchesSelection(binding, request)
743
+ : hostMcpBindingMatchesRequest(binding, request))
739
744
  )
740
745
  return false;
741
746
  const live = await getHostMcpLiveAttempt(
@@ -752,3 +757,21 @@ async function resolveDirectHostMcpUseAuthority(
752
757
  );
753
758
  });
754
759
  }
760
+
761
+ /** Resolve a configuration selector from the exact live accepted turn, never
762
+ * registry inventory or session creator metadata. The broker still revalidates
763
+ * the resulting concrete reference around resolution and every physical use. */
764
+ export async function resolveAcceptedHostMcpBinding(
765
+ db: Database,
766
+ request: McpCredentialsRequest,
767
+ ): Promise<McpCredentialsRequest["connectionRef"] | null> {
768
+ if (!request.connectionRef.hostBinding || !("selection" in request.connectionRef.hostBinding))
769
+ return null;
770
+ const authority = await resolveDirectHostMcpUseAuthority(db, request);
771
+ return authority
772
+ ? {
773
+ ...authority.definition.connectionRef,
774
+ hostBinding: { bindingId: authority.bindingId, generation: authority.bindingGeneration },
775
+ }
776
+ : null;
777
+ }