@agent-native/core 0.212.1 → 0.214.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 (107) hide show
  1. package/dist/a2a/anthropic-managed-agents.js +1 -0
  2. package/dist/a2a/caller-auth.js +7 -2
  3. package/dist/a2a/client.d.ts +1 -0
  4. package/dist/a2a/client.js +27 -0
  5. package/dist/a2a/handlers.js +15 -3
  6. package/dist/a2a/organization-identity.d.ts +4 -1
  7. package/dist/a2a/organization-identity.js +3 -1
  8. package/dist/a2a/server.d.ts +6 -5
  9. package/dist/a2a/server.js +79 -6
  10. package/dist/a2a-claims.d.ts +1 -0
  11. package/dist/a2a-claims.js +1 -0
  12. package/dist/agent/actions/manage-provider-key-policy.js +4 -2
  13. package/dist/agent/app-model-defaults.js +2 -10
  14. package/dist/agent/loop-settings.js +2 -10
  15. package/dist/appearance/actions/get-theme-family.d.ts +8 -0
  16. package/dist/appearance/actions/get-theme-family.js +38 -0
  17. package/dist/appearance/actions/set-theme-family.d.ts +7 -0
  18. package/dist/appearance/actions/set-theme-family.js +33 -0
  19. package/dist/appearance/theme-family.d.ts +13 -0
  20. package/dist/appearance/theme-family.js +38 -0
  21. package/dist/audit/actions/export-audit-events.js +1 -0
  22. package/dist/audit/attribution.d.ts +5 -4
  23. package/dist/audit/attribution.js +2 -1
  24. package/dist/audit/index.d.ts +1 -1
  25. package/dist/audit/ocsf.js +1 -0
  26. package/dist/audit/read-scope.js +2 -12
  27. package/dist/audit/record.js +1 -0
  28. package/dist/audit/store.js +12 -3
  29. package/dist/audit/types.d.ts +4 -0
  30. package/dist/automation/index.d.ts +1 -1
  31. package/dist/automations/service.js +3 -10
  32. package/dist/client/org/hooks.d.ts +3 -0
  33. package/dist/client/org/hooks.js +12 -0
  34. package/dist/client/theme-family.d.ts +35 -0
  35. package/dist/client/theme-family.js +223 -0
  36. package/dist/client/theme.d.ts +1 -1
  37. package/dist/client/theme.js +5 -2
  38. package/dist/client/ui/index.d.ts +1 -0
  39. package/dist/client/ui/index.js +1 -0
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/routes.d.ts +2 -2
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/connections/catalog.d.ts +1 -1
  44. package/dist/connections/catalog.js +7 -1
  45. package/dist/credentials/index.js +18 -2
  46. package/dist/data-widgets/index.d.ts +339 -4
  47. package/dist/data-widgets/index.js +150 -7
  48. package/dist/db/schema-snapshot.generated.js +5 -2
  49. package/dist/feature-flags/a2a-action-route.js +1 -0
  50. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  51. package/dist/file-upload/storage-settings.js +2 -9
  52. package/dist/framework-tools.js +2 -0
  53. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  54. package/dist/jobs/tools.js +2 -12
  55. package/dist/mcp/actions/service-token-access.js +15 -11
  56. package/dist/mcp/credential-issuance.js +9 -2
  57. package/dist/notifications/routes.d.ts +3 -3
  58. package/dist/observability/routes.d.ts +1 -1
  59. package/dist/org/actions.d.ts +1 -1
  60. package/dist/org/actions.js +3 -7
  61. package/dist/org/app-roles.js +10 -1
  62. package/dist/org/context.d.ts +1 -0
  63. package/dist/org/context.js +122 -24
  64. package/dist/org/federation.d.ts +1 -1
  65. package/dist/org/federation.js +2 -2
  66. package/dist/org/handlers.d.ts +4 -0
  67. package/dist/org/handlers.js +140 -44
  68. package/dist/org/index.d.ts +2 -1
  69. package/dist/org/membership.d.ts +6 -4
  70. package/dist/org/membership.js +7 -5
  71. package/dist/org/migrations.js +6 -0
  72. package/dist/org/plugin.d.ts +1 -0
  73. package/dist/org/plugin.js +9 -1
  74. package/dist/org/private-icon-handlers.js +4 -10
  75. package/dist/org/types.d.ts +2 -0
  76. package/dist/org/workspace-app-access.js +62 -14
  77. package/dist/progress/routes.d.ts +1 -1
  78. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  79. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  80. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  81. package/dist/secrets/routes.d.ts +6 -6
  82. package/dist/server/action-discovery.js +8 -0
  83. package/dist/server/action-routes.d.ts +1 -0
  84. package/dist/server/action-routes.js +13 -1
  85. package/dist/server/agent-chat/context-tools.js +43 -1
  86. package/dist/server/agent-chat/prompt-resources.js +1 -1
  87. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  88. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  89. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +1 -0
  92. package/dist/server/org-admin.js +3 -2
  93. package/dist/server/personal-provider-key-holders.d.ts +1 -1
  94. package/dist/server/personal-provider-key-holders.js +7 -2
  95. package/dist/server/personal-provider-key-policy.d.ts +2 -0
  96. package/dist/server/personal-provider-key-policy.js +21 -5
  97. package/dist/server/request-context.d.ts +2 -0
  98. package/dist/server/request-context.js +7 -0
  99. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
  100. package/dist/triggers/actions/manage-automation.d.ts +4 -4
  101. package/dist/triggers/routes.js +2 -9
  102. package/dist/usage/alerts-store.js +2 -9
  103. package/dist/usage/metrics-store.js +2 -9
  104. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
  105. package/dist/workspace-connections/groups.js +2 -11
  106. package/package.json +3 -3
  107. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
@@ -16,7 +16,7 @@ export const SCHEMA_SNAPSHOT = {
16
16
  'CREATE TABLE "a2a_approvals" (\n "id" text NOT NULL,\n "task_id" text NOT NULL,\n "owner_email" text NOT NULL,\n "org_id" text,\n "tool_name" text NOT NULL,\n "tool_input" text NOT NULL,\n "approval_key" text NOT NULL,\n "call_id" text NOT NULL,\n "status" text DEFAULT \'pending\'::text NOT NULL,\n "result" text,\n "expires_at" bigint NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL\n);',
17
17
  'CREATE TABLE "a2a_tasks" (\n "id" text NOT NULL,\n "context_id" text,\n "status_state" text DEFAULT \'submitted\'::text NOT NULL,\n "status_message" text,\n "status_timestamp" text NOT NULL,\n "history" text DEFAULT \'[]\'::text NOT NULL,\n "artifacts" text DEFAULT \'[]\'::text NOT NULL,\n "metadata" text,\n "owner_email" text,\n "owner_scope" text DEFAULT \'\'::text NOT NULL,\n "idempotency_key" text,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL\n);',
18
18
  'CREATE TABLE "account" (\n "id" text NOT NULL,\n "account_id" text NOT NULL,\n "provider_id" text NOT NULL,\n "user_id" text NOT NULL,\n "access_token" text,\n "refresh_token" text,\n "id_token" text,\n "access_token_expires_at" timestamp with time zone,\n "refresh_token_expires_at" timestamp with time zone,\n "scope" text,\n "password" text,\n "created_at" timestamp with time zone NOT NULL,\n "updated_at" timestamp with time zone NOT NULL\n);',
19
- 'CREATE TABLE "agent_audit_log" (\n "id" text NOT NULL,\n "created_at" bigint NOT NULL,\n "action" text NOT NULL,\n "caller" text NOT NULL,\n "actor_kind" text NOT NULL,\n "actor_email" text,\n "org_id" text,\n "thread_id" text,\n "turn_id" text,\n "target_type" text,\n "target_id" text,\n "status" text DEFAULT \'success\'::text NOT NULL,\n "summary" text,\n "input" text,\n "error_code" text,\n "owner_email" text,\n "visibility" text DEFAULT \'private\'::text NOT NULL,\n "run_id" text,\n "task_id" text,\n "parent_task_id" text,\n "source_kind" text,\n "source_platform" text,\n "source_id" text,\n "source_url" text,\n "network_protocol" text,\n "network_id" text,\n "network_peer" text,\n "app" text,\n "reported_client_name" text,\n "reported_client_version" text,\n "oauth_client_id" text,\n "approval" text\n);',
19
+ 'CREATE TABLE "agent_audit_log" (\n "id" text NOT NULL,\n "created_at" bigint NOT NULL,\n "action" text NOT NULL,\n "caller" text NOT NULL,\n "actor_kind" text NOT NULL,\n "actor_email" text,\n "org_id" text,\n "thread_id" text,\n "turn_id" text,\n "target_type" text,\n "target_id" text,\n "status" text DEFAULT \'success\'::text NOT NULL,\n "summary" text,\n "input" text,\n "error_code" text,\n "owner_email" text,\n "visibility" text DEFAULT \'private\'::text NOT NULL,\n "run_id" text,\n "task_id" text,\n "parent_task_id" text,\n "source_kind" text,\n "source_platform" text,\n "source_id" text,\n "source_url" text,\n "network_protocol" text,\n "network_id" text,\n "network_peer" text,\n "app" text,\n "reported_client_name" text,\n "reported_client_version" text,\n "oauth_client_id" text,\n "approval" text,\n "identity_assurance" text\n);',
20
20
  'CREATE TABLE "agent_chat_submission_inbox" (\n "thread_id" text NOT NULL,\n "message_id" text NOT NULL,\n "state" text NOT NULL,\n "actor_email" text NOT NULL,\n "actor_auth_user_id" text,\n "actor_identity_authenticated_at_ms" bigint,\n "actor_name" text,\n "actor_org_id" text,\n "actor_org_scope" text,\n "actor_anonymous" boolean DEFAULT false NOT NULL,\n "payload_hash" text NOT NULL,\n "payload_json" text NOT NULL,\n "interrupt_active_run" boolean DEFAULT false NOT NULL,\n "submitted_run_id" text,\n "queue_order" bigint DEFAULT nextval(\'agent_chat_submission_inbox_queue_order_seq\'::regclass) NOT NULL,\n "move_to_top_at" bigint,\n "retry_after" bigint,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "interrupt_run_id" text\n);',
21
21
  'CREATE TABLE "agent_checkpoints" (\n "id" text NOT NULL,\n "thread_id" text NOT NULL,\n "run_id" text,\n "commit_sha" text NOT NULL,\n "message" text DEFAULT \'\'::text NOT NULL,\n "created_at" bigint NOT NULL\n);',
22
22
  'CREATE TABLE "agent_eval_datasets" (\n "id" text NOT NULL,\n "name" text NOT NULL,\n "description" text DEFAULT \'\'::text NOT NULL,\n "entries" text DEFAULT \'[]\'::text NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "user_id" text,\n "idempotency_key" text\n);',
@@ -121,7 +121,7 @@ export const SCHEMA_SNAPSHOT = {
121
121
  'CREATE TABLE "org_members" (\n "id" text NOT NULL,\n "org_id" text NOT NULL,\n "email" text NOT NULL,\n "role" text NOT NULL,\n "joined_at" bigint NOT NULL,\n "federation_removal_pending_at" bigint\n);',
122
122
  'CREATE TABLE "org_scim_memberships" (\n "id" text NOT NULL,\n "org_id" text NOT NULL,\n "user_id" text NOT NULL,\n "created_membership" boolean DEFAULT false NOT NULL,\n "created_at" bigint NOT NULL,\n "member_id" text\n);',
123
123
  'CREATE TABLE "organization" (\n "id" text NOT NULL,\n "name" text NOT NULL,\n "slug" text NOT NULL,\n "logo" text,\n "metadata" text,\n "created_at" timestamp with time zone NOT NULL,\n "updated_at" timestamp with time zone NOT NULL\n);',
124
- 'CREATE TABLE "organizations" (\n "id" text NOT NULL,\n "name" text NOT NULL,\n "created_by" text NOT NULL,\n "created_at" bigint NOT NULL,\n "allowed_domain" text,\n "a2a_secret" text,\n "workspace_url" text,\n "required_auth_provider" text,\n "identity_authority" text,\n "identity_id" text,\n "federation_roster_initialized_at" bigint,\n "icon_json" text,\n "icon_revision" bigint DEFAULT 0 NOT NULL\n);',
124
+ 'CREATE TABLE "organizations" (\n "id" text NOT NULL,\n "name" text NOT NULL,\n "created_by" text NOT NULL,\n "created_at" bigint NOT NULL,\n "allowed_domain" text,\n "a2a_secret" text,\n "workspace_url" text,\n "required_auth_provider" text,\n "identity_authority" text,\n "identity_id" text,\n "federation_roster_initialized_at" bigint,\n "icon_json" text,\n "icon_revision" bigint DEFAULT 0 NOT NULL,\n "a2a_member_delegation_enabled" boolean DEFAULT false NOT NULL\n);',
125
125
  'CREATE TABLE "private_icon_assets" (\n "id" text NOT NULL,\n "owner_email" text NOT NULL,\n "org_id" text,\n "mime_type" text NOT NULL,\n "filename" text,\n "alt" text,\n "size" bigint NOT NULL,\n "sha256" text NOT NULL,\n "blob_handle_json" text NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL\n);',
126
126
  'CREATE TABLE "progress_runs" (\n "id" text NOT NULL,\n "owner" text NOT NULL,\n "title" text NOT NULL,\n "step" text,\n "percent" bigint,\n "status" text DEFAULT \'running\'::text NOT NULL,\n "metadata" text,\n "started_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "completed_at" bigint\n);',
127
127
  'CREATE TABLE "provider_api_cooldowns" (\n "quota_key" text NOT NULL,\n "provider_id" text NOT NULL,\n "scope_key" text NOT NULL,\n "cooldown_until" bigint NOT NULL,\n "status" bigint,\n "reason" text,\n "updated_at" bigint NOT NULL\n);',
@@ -866,6 +866,7 @@ export const SCHEMA_SNAPSHOT = {
866
866
  ["application_state", "baseline", null],
867
867
  ["application_state", "indexes", null],
868
868
  ["application_state", "widen-bigint", null],
869
+ ["audit", "a2a-identity-assurance", "5ebfefc25224eaa5"],
869
870
  ["audit", "baseline", null],
870
871
  ["audit", "mcp-client-attribution", "bc0ea72722e10241"],
871
872
  ["auth_sessions", "baseline", null],
@@ -1038,6 +1039,7 @@ export const SCHEMA_SNAPSHOT = {
1038
1039
  ["org", "org-scim-membership-member-id", "cd1c4fb7479507d3"],
1039
1040
  ["org", "org-scim-membership-ownership", "09ca1c03026cda83"],
1040
1041
  ["org", "org-tables-timestamps-bigint", "a71b72b885bd5585"],
1042
+ ["org", "organization-a2a-member-delegation", "3d01bcd4e7c259b0"],
1041
1043
  ["org", "organization-federation-removal-pending", "47fe40eb2ea33753"],
1042
1044
  ["org", "organization-federation-roster-initialized", "3307d4d99818c2f0"],
1043
1045
  ["org", "organization-identity-federation", "e24bac924c2d4c7a"],
@@ -1182,5 +1184,6 @@ export const SCHEMA_SNAPSHOT = {
1182
1184
  ["_org_migrations", 1032, "agent-audit-log-base-table"],
1183
1185
  ["_org_migrations", 1033, "organization-visual-identity"],
1184
1186
  ["_org_migrations", 1034, "private-icon-assets"],
1187
+ ["_org_migrations", 1035, "organization-a2a-member-delegation"],
1185
1188
  ],
1186
1189
  };
@@ -46,6 +46,7 @@ export function createFeatureFlagA2AActionRouteAuth(actionName) {
46
46
  owner: claims.email,
47
47
  orgId,
48
48
  anonymous: false,
49
+ identityAssurance: claims.identityAssurance,
49
50
  delegationJti: claims.jti,
50
51
  delegationIssuer: claims.issuer,
51
52
  };
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- configured?: undefined;
46
45
  error?: undefined;
46
+ configured?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -8,8 +8,8 @@
8
8
  * keep uploading to the old bucket.
9
9
  */
10
10
  import { fail } from "../action.js";
11
- import { getDbExec } from "../db/client.js";
12
11
  import { deleteAppSecret, readAppSecrets, writeAppSecret, } from "../secrets/storage.js";
12
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
13
13
  import { getActiveFileUploadProviderForRequest, listFileUploadProviders, } from "./registry.js";
14
14
  import { getPrivateStorageReadinessForUser, } from "./storage-readiness.js";
15
15
  export const FILE_STORAGE_SECRET_KEYS = {
@@ -46,14 +46,7 @@ export async function resolveFileStorageAccess(ctx) {
46
46
  // matching `canMutateWorkspaceScope` in the secrets routes.
47
47
  if (!orgId)
48
48
  return { scopeId: `solo:${email}`, canManage: true };
49
- const { rows } = await getDbExec().execute({
50
- sql: `SELECT role FROM org_members
51
- WHERE org_id = ? AND LOWER(email) = LOWER(?)
52
- AND federation_removal_pending_at IS NULL
53
- LIMIT 1`,
54
- args: [orgId, email],
55
- });
56
- const role = rows[0]?.role;
49
+ const role = await readOrgMemberRole(orgId, email);
57
50
  return { scopeId: orgId, canManage: role === "owner" || role === "admin" };
58
51
  }
59
52
  export function inferFileStorageProvider(endpoint) {
@@ -165,6 +165,8 @@ export const CORE_ACTION_GROUPS = {
165
165
  "change-password": "userProfile",
166
166
  "request-privacy-right": "userProfile",
167
167
  "change-appearance": "userProfile",
168
+ "get-theme-family": "userProfile",
169
+ "set-theme-family": "userProfile",
168
170
  "list-audit-events": "audit",
169
171
  "get-audit-event": "audit",
170
172
  "export-audit-events": "audit",
@@ -13,10 +13,10 @@ declare const _default: import("../../action.js").ActionDefinition<{
13
13
  enabled?: undefined;
14
14
  nextRun?: undefined;
15
15
  } | {
16
+ deleted?: undefined;
16
17
  resourceId: string;
17
18
  name: string;
18
19
  enabled: boolean;
19
20
  nextRun: string;
20
- deleted?: undefined;
21
21
  }>;
22
22
  export default _default;
@@ -1,8 +1,8 @@
1
1
  import { resolveAutomationAccess } from "../automations/access.js";
2
2
  import { DEFAULT_AUTOMATION_SCHEDULE } from "../automations/service.js";
3
- import { getDbExec } from "../db/client.js";
4
3
  import { resolveUserSchedulingTimezone } from "../localization/user-timezone.js";
5
4
  import { organizationIdFromResourceOwner, resourcePutIfAbsent, resourceGetByPath, resourceList, resourceDelete, SHARED_OWNER, sharedResourceOwner, } from "../resources/store.js";
5
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
6
6
  import { getRequestUserEmail, getRequestOrgId, getIntegrationRequestContext, } from "../server/request-context.js";
7
7
  import { isReasoningEffort, REASONING_EFFORTS, } from "../shared/reasoning-effort.js";
8
8
  import { RESUME_AUTOMATION_PATCH } from "./automation-outcome.js";
@@ -27,17 +27,7 @@ async function isCurrentUserOrgAdmin(orgId) {
27
27
  if (!email)
28
28
  return false;
29
29
  try {
30
- const client = getDbExec();
31
- const { rows } = await client.execute({
32
- sql: `SELECT role FROM org_members
33
- WHERE org_id = ? AND LOWER(email) = ?
34
- AND federation_removal_pending_at IS NULL
35
- LIMIT 1`,
36
- args: [orgId, email.toLowerCase()],
37
- });
38
- if (rows.length === 0)
39
- return false;
40
- const role = String(rows[0].role ?? "").toLowerCase();
30
+ const role = await readOrgMemberRole(orgId, email);
41
31
  return role === "owner" || role === "admin";
42
32
  }
43
33
  catch {
@@ -14,6 +14,8 @@
14
14
  import { getDbExec } from "../../db/client.js";
15
15
  import { isMissingOrganizationTableError } from "../../org/membership.js";
16
16
  import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE } from "../../server/credential-membership-unavailable.js";
17
+ import { readOrgMemberRole } from "../../server/personal-provider-key-policy.js";
18
+ import { getRequestContext } from "../../server/request-context.js";
17
19
  export const SERVICE_TOKEN_MANAGE_FORBIDDEN_MESSAGE = "Only org owners or admins can create or revoke service tokens.";
18
20
  export class ServiceTokenError extends Error {
19
21
  statusCode;
@@ -24,17 +26,7 @@ export class ServiceTokenError extends Error {
24
26
  }
25
27
  }
26
28
  async function readOrgRole(orgId, email) {
27
- const { rows } = await getDbExec().execute({
28
- sql: `SELECT role FROM org_members
29
- WHERE org_id = ? AND LOWER(email) = ?
30
- AND federation_removal_pending_at IS NULL
31
- LIMIT 1`,
32
- args: [orgId, email.toLowerCase()],
33
- });
34
- const role = rows[0]?.role;
35
- return role === "owner" || role === "admin" || role === "member"
36
- ? role
37
- : null;
29
+ return readOrgMemberRole(orgId, email);
38
30
  }
39
31
  export async function getOrgRoleForEmail(orgId, email) {
40
32
  try {
@@ -76,6 +68,18 @@ export async function requireServiceTokenCaller(params) {
76
68
  throw new ServiceTokenError("Sign in to manage org service tokens.", 401);
77
69
  }
78
70
  let orgId = params.orgId?.trim() || "";
71
+ const requestContext = getRequestContext();
72
+ if (requestContext?.a2aIdentityAssurance === "org-member") {
73
+ const delegatedEmail = requestContext.userEmail?.trim().toLowerCase();
74
+ const delegatedOrgId = requestContext.orgId?.trim();
75
+ if (!delegatedEmail ||
76
+ delegatedEmail !== email.toLowerCase() ||
77
+ !delegatedOrgId ||
78
+ (orgId && orgId !== delegatedOrgId)) {
79
+ throw new ServiceTokenError("You are not a member of this organization.", 403);
80
+ }
81
+ orgId = delegatedOrgId;
82
+ }
79
83
  if (!orgId) {
80
84
  const memberOrgs = await lookupMembership(() => readOrgIds(email), []);
81
85
  if (memberOrgs.length === 0) {
@@ -1,5 +1,7 @@
1
1
  import { getDbExec } from "../db/client.js";
2
2
  import { identityCredentialLockKey, readEmailRetiredAt, } from "../identity/retired-emails.js";
3
+ import { effectiveOrgMemberRole } from "../server/personal-provider-key-policy.js";
4
+ import { getRequestContext } from "../server/request-context.js";
3
5
  import { checkCredentialOrgMembership } from "./credential-membership.js";
4
6
  export class McpCredentialIssuanceError extends Error {
5
7
  reason;
@@ -19,6 +21,11 @@ export async function withMcpCredentialIssuance(input, run) {
19
21
  const email = input.email.trim().toLowerCase();
20
22
  const human = input.kind !== "service";
21
23
  const requiresMembership = orgId !== null && human;
24
+ if (getRequestContext()?.a2aIdentityAssurance === "org-member") {
25
+ if (!orgId || !effectiveOrgMemberRole("member", orgId, email)) {
26
+ throw new McpCredentialIssuanceError("not-member");
27
+ }
28
+ }
22
29
  if (requiresMembership) {
23
30
  // Federation validation may update membership on another connection.
24
31
  // Finish it before acquiring the local offboarding lock.
@@ -60,9 +67,9 @@ export async function withMcpCredentialIssuance(input, run) {
60
67
  FOR UPDATE`,
61
68
  args: [orgId, email],
62
69
  });
70
+ const role = effectiveOrgMemberRole(rows[0]?.role, orgId, email);
63
71
  if (rows.length === 0 ||
64
- (input.roles &&
65
- !input.roles.includes(String(rows[0].role)))) {
72
+ (input.roles && !input.roles.includes(role))) {
66
73
  throw new McpCredentialIssuanceError("not-member");
67
74
  }
68
75
  }
@@ -1,8 +1,8 @@
1
1
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
2
2
  count: number;
3
+ updated?: undefined;
3
4
  error?: undefined;
4
5
  ok?: undefined;
5
- updated?: undefined;
6
6
  } | {
7
7
  count?: undefined;
8
8
  updated: number;
@@ -10,12 +10,12 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
10
10
  ok?: undefined;
11
11
  } | {
12
12
  count?: undefined;
13
+ updated?: undefined;
13
14
  error: string;
14
15
  ok?: undefined;
15
- updated?: undefined;
16
16
  } | {
17
17
  count?: undefined;
18
+ updated?: undefined;
18
19
  ok: boolean;
19
20
  error?: undefined;
20
- updated?: undefined;
21
21
  }>>;
@@ -64,6 +64,6 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
64
64
  message?: undefined;
65
65
  summary?: undefined;
66
66
  spans?: undefined;
67
- ok: boolean;
68
67
  error?: undefined;
68
+ ok: boolean;
69
69
  }>>;
@@ -2,5 +2,5 @@ import { type ActionRunContext } from "../action.js";
2
2
  export declare function requireOrgMember(ctx?: ActionRunContext, admin?: boolean): Promise<{
3
3
  email: string;
4
4
  orgId: string;
5
- role: string;
5
+ role: import("./types.js").OrgRole;
6
6
  }>;
@@ -1,5 +1,5 @@
1
1
  import { fail } from "../action.js";
2
- import { getDbExec } from "../db/client.js";
2
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
3
3
  export async function requireOrgMember(ctx, admin = false) {
4
4
  const email = ctx?.userEmail?.trim();
5
5
  const orgId = ctx?.orgId?.trim();
@@ -8,11 +8,7 @@ export async function requireOrgMember(ctx, admin = false) {
8
8
  errorCode: "unauthorized",
9
9
  statusCode: 401,
10
10
  });
11
- const { rows } = await getDbExec().execute({
12
- sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = LOWER(?) AND federation_removal_pending_at IS NULL LIMIT 1`,
13
- args: [orgId, email],
14
- });
15
- const role = rows[0]?.role;
11
+ const role = await readOrgMemberRole(orgId, email);
16
12
  if (!role)
17
13
  fail("You are not a member of the active organization.", {
18
14
  errorCode: "forbidden",
@@ -23,5 +19,5 @@ export async function requireOrgMember(ctx, admin = false) {
23
19
  errorCode: "forbidden",
24
20
  statusCode: 403,
25
21
  });
26
- return { email, orgId, role: String(role) };
22
+ return { email, orgId, role };
27
23
  }
@@ -1,5 +1,6 @@
1
1
  import { getDbExec } from "../db/client.js";
2
- import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
+ import { effectiveOrgMemberRole } from "../server/personal-provider-key-policy.js";
3
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
3
4
  import { ForbiddenError } from "../sharing/access.js";
4
5
  import { isMissingOrganizationTableError } from "./membership.js";
5
6
  const registry = new Map();
@@ -137,6 +138,9 @@ export async function resolveAppRole(descriptor, caller) {
137
138
  return { status: "no-identity" };
138
139
  if (!orgId)
139
140
  return { status: "no-org" };
141
+ if (!effectiveOrgMemberRole("member", orgId, email)) {
142
+ return { status: "not-a-member", orgId };
143
+ }
140
144
  let rows;
141
145
  try {
142
146
  rows = (await getDbExec().execute({
@@ -187,6 +191,11 @@ export async function resolveAppRole(descriptor, caller) {
187
191
  return { status: "not-a-member", orgId };
188
192
  orgRole = membership.role;
189
193
  }
194
+ const isDelegatedOrgMember = getRequestContext()?.a2aIdentityAssurance === "org-member";
195
+ orgRole = effectiveOrgMemberRole(orgRole, orgId, email) ?? undefined;
196
+ if (isDelegatedOrgMember && !orgRole) {
197
+ return { status: "not-a-member", orgId };
198
+ }
190
199
  const raw = row.roles ?? row.appRoles ?? row.approles;
191
200
  const roles = Array.isArray(raw) ? raw.map(String) : [];
192
201
  const validRoles = [
@@ -85,5 +85,6 @@ export declare function resolveA2AOrganizationCredentialsByDomain(domain: string
85
85
  orgId: string;
86
86
  orgDomain: string;
87
87
  secret: string;
88
+ a2aMemberDelegationEnabled: boolean;
88
89
  } | null>;
89
90
  export declare function isSoleOrgDomain(domain: string): Promise<boolean>;
@@ -7,6 +7,7 @@ import { getDbExec, isTransientDatabaseError } from "../db/client.js";
7
7
  import { isAnonymousWaitlistSessionEmail } from "../server/anonymous-identity.js";
8
8
  import { crossSiteCookieAttrs, getSession } from "../server/auth.js";
9
9
  import { shouldWriteFirstRunOnboardingEligibility } from "../server/first-run-onboarding-build-mode.js";
10
+ import { effectiveOrgMemberRole, readOrgMemberRole, } from "../server/personal-provider-key-policy.js";
10
11
  import { getVerifiedServiceIdentityFromEvent, getRequestContext, hasExplicitPersonalOrgScope, } from "../server/request-context.js";
11
12
  import { getSetting } from "../settings/store.js";
12
13
  import { getUserSetting } from "../settings/user-settings.js";
@@ -46,6 +47,32 @@ function normalizeOrgRole(value) {
46
47
  ? value
47
48
  : null;
48
49
  }
50
+ function delegatedOrgMemberScope(email) {
51
+ const requestContext = getRequestContext();
52
+ if (requestContext?.a2aIdentityAssurance !== "org-member")
53
+ return undefined;
54
+ const requestEmail = requestContext.userEmail?.trim().toLowerCase();
55
+ const requestOrgId = requestContext.orgId?.trim();
56
+ if (!requestEmail ||
57
+ !requestOrgId ||
58
+ (email && email.trim().toLowerCase() !== requestEmail)) {
59
+ return null;
60
+ }
61
+ return requestOrgId;
62
+ }
63
+ function scopeMembershipsForDelegatedIdentity(email, memberships) {
64
+ const scopedOrgId = delegatedOrgMemberScope(email);
65
+ if (scopedOrgId === undefined || memberships === null)
66
+ return memberships;
67
+ if (!scopedOrgId)
68
+ return [];
69
+ return memberships.flatMap((membership) => {
70
+ if (membership.orgId !== scopedOrgId)
71
+ return [];
72
+ const role = effectiveOrgMemberRole(membership.role, scopedOrgId, email);
73
+ return role ? [{ ...membership, role }] : [];
74
+ });
75
+ }
49
76
  function emailDomainOf(email) {
50
77
  return email.split("@")[1]?.toLowerCase() || null;
51
78
  }
@@ -94,10 +121,13 @@ async function refreshFederatedMemberships(event, email, memberships, selectedOr
94
121
  if (!result.active) {
95
122
  return memberships.filter((item) => item.orgId !== selected.orgId);
96
123
  }
97
- if (result.role === selected.role) {
124
+ const role = effectiveOrgMemberRole(result.role, selected.orgId, email);
125
+ if (!role)
126
+ return memberships.filter((item) => item.orgId !== selected.orgId);
127
+ if (role === selected.role) {
98
128
  return memberships;
99
129
  }
100
- return memberships.map((item) => item.orgId === selected.orgId ? { ...item, role: result.role } : item);
130
+ return memberships.map((item) => item.orgId === selected.orgId ? { ...item, role } : item);
101
131
  }
102
132
  const MEMBERSHIPS_CACHE_KEY = "__anOrgMembershipsCache";
103
133
  const ACTIVE_ORG_SETTING_CACHE_KEY = "__anActiveOrgSettingCache";
@@ -143,12 +173,19 @@ export function markActiveOrgSelectionChanged(event) {
143
173
  });
144
174
  }
145
175
  function loadMembershipsForEvent(event, email) {
176
+ const scopedOrgId = delegatedOrgMemberScope(email);
177
+ if (scopedOrgId === null)
178
+ return Promise.resolve([]);
146
179
  const ctx = event.context;
147
180
  const cache = (ctx[MEMBERSHIPS_CACHE_KEY] ??
148
181
  (ctx[MEMBERSHIPS_CACHE_KEY] = new Map()));
149
182
  let promise = cache.get(email);
150
183
  if (!promise) {
151
- promise = loadMemberships(email).catch((err) => {
184
+ promise = loadMemberships(email)
185
+ .then((memberships) => scopedOrgId === undefined
186
+ ? memberships
187
+ : scopeMembershipsForDelegatedIdentity(email, memberships))
188
+ .catch((err) => {
152
189
  cache.delete(email);
153
190
  throw err;
154
191
  });
@@ -166,17 +203,25 @@ async function resolveOrgContextUncached(event) {
166
203
  const email = session?.email;
167
204
  if (!email)
168
205
  return EMPTY_CONTEXT;
206
+ const requestContext = getRequestContext();
207
+ const isDelegatedOrgMember = requestContext?.a2aIdentityAssurance === "org-member";
208
+ const delegatedOrgId = delegatedOrgMemberScope(email);
209
+ if (isDelegatedOrgMember && !delegatedOrgId) {
210
+ return { email, orgId: null, orgName: null, role: null };
211
+ }
169
212
  if (isAnonymousWaitlistSessionEmail(email)) {
170
213
  return { email, orgId: null, orgName: null, role: null };
171
214
  }
172
- if (hasExplicitPersonalOrgScope(event)) {
215
+ if (!isDelegatedOrgMember && hasExplicitPersonalOrgScope(event)) {
173
216
  return { email, orgId: null, orgName: null, role: null };
174
217
  }
175
- const sessionOrgId = typeof session.orgId === "string" && session.orgId.trim()
176
- ? session.orgId.trim()
177
- : null;
178
- const sessionOrgRole = normalizeOrgRole(session.orgRole);
179
- const requestContext = getRequestContext();
218
+ const sessionOrgId = delegatedOrgId ??
219
+ (typeof session.orgId === "string" && session.orgId.trim()
220
+ ? session.orgId.trim()
221
+ : null);
222
+ const sessionOrgRole = isDelegatedOrgMember
223
+ ? "member"
224
+ : normalizeOrgRole(session.orgRole);
180
225
  const verifiedServiceIdentity = getVerifiedServiceIdentityFromEvent(event);
181
226
  const serviceRole = implicitServiceOrgRole({
182
227
  email,
@@ -209,7 +254,9 @@ async function resolveOrgContextUncached(event) {
209
254
  }
210
255
  }
211
256
  const exec = getDbExec();
212
- const activeOrgSettingPromise = loadActiveOrgSettingForEvent(event, email);
257
+ const activeOrgSettingPromise = isDelegatedOrgMember
258
+ ? Promise.resolve(null)
259
+ : loadActiveOrgSettingForEvent(event, email);
213
260
  activeOrgSettingPromise.catch(() => { });
214
261
  let memberships;
215
262
  try {
@@ -219,6 +266,10 @@ async function resolveOrgContextUncached(event) {
219
266
  // A transient membership read must not downgrade an authenticated request
220
267
  // to a private/solo scope when the session already carries its org.
221
268
  if (sessionOrgId && isTransientDatabaseError(err)) {
269
+ if (isDelegatedOrgMember) {
270
+ markOrgScopeResolutionUnavailable(event);
271
+ return { email, orgId: null, orgName: null, role: null };
272
+ }
222
273
  return {
223
274
  email,
224
275
  orgId: sessionOrgId,
@@ -231,6 +282,9 @@ async function resolveOrgContextUncached(event) {
231
282
  if (memberships === null) {
232
283
  // A null membership read is unavailable scope, not proof of a solo user.
233
284
  markOrgScopeResolutionUnavailable(event);
285
+ if (isDelegatedOrgMember) {
286
+ return { email, orgId: null, orgName: null, role: null };
287
+ }
234
288
  if (sessionOrgId) {
235
289
  return {
236
290
  email,
@@ -242,16 +296,18 @@ async function resolveOrgContextUncached(event) {
242
296
  return { email, orgId: null, orgName: null, role: null };
243
297
  }
244
298
  const activeOrgSetting = await activeOrgSettingPromise;
245
- const explicitPersonal = activeOrgSetting?.orgId === null;
246
- const selectedMembership = explicitPersonal
247
- ? undefined
248
- : ((activeOrgSetting?.orgId
249
- ? memberships.find((membership) => membership.orgId === activeOrgSetting.orgId)
250
- : undefined) ??
251
- (sessionOrgId
252
- ? memberships.find((membership) => membership.orgId === sessionOrgId)
299
+ const explicitPersonal = !isDelegatedOrgMember && activeOrgSetting?.orgId === null;
300
+ const selectedMembership = isDelegatedOrgMember
301
+ ? memberships.find((membership) => membership.orgId === delegatedOrgId)
302
+ : explicitPersonal
303
+ ? undefined
304
+ : ((activeOrgSetting?.orgId
305
+ ? memberships.find((membership) => membership.orgId === activeOrgSetting.orgId)
253
306
  : undefined) ??
254
- memberships[0]);
307
+ (sessionOrgId
308
+ ? memberships.find((membership) => membership.orgId === sessionOrgId)
309
+ : undefined) ??
310
+ memberships[0]);
255
311
  const selectedOrgId = selectedMembership?.orgId ?? null;
256
312
  const selectedWasFederated = Boolean(selectedMembership?.identityAuthority || selectedMembership?.identityId);
257
313
  const refreshedMemberships = await refreshFederatedMemberships(event, email, memberships, selectedOrgId);
@@ -268,7 +324,8 @@ async function resolveOrgContextUncached(event) {
268
324
  markFederationMembershipValidated(event, email, selectedOrgId);
269
325
  }
270
326
  const emailDomain = emailDomainOf(email);
271
- const shouldTryDomainAutoJoin = !explicitPersonal &&
327
+ const shouldTryDomainAutoJoin = !isDelegatedOrgMember &&
328
+ !explicitPersonal &&
272
329
  session.emailVerified === true &&
273
330
  emailDomain !== null &&
274
331
  !isFreeEmailProvider(emailDomain) &&
@@ -358,7 +415,9 @@ async function resolveOrgContextUncached(event) {
358
415
  }
359
416
  return { email, orgId: null, orgName: null, role: null };
360
417
  }
361
- if (memberships.length === 0 && autoCreateDefaultOrgEnabled()) {
418
+ if (!isDelegatedOrgMember &&
419
+ memberships.length === 0 &&
420
+ autoCreateDefaultOrgEnabled()) {
362
421
  const created = await tryCreateDefaultOrg(exec, email, session, event);
363
422
  if (created)
364
423
  return created;
@@ -381,7 +440,8 @@ async function loadMemberships(email) {
381
440
  return cachedMemberships(email, () => loadMembershipsUncached(email));
382
441
  }
383
442
  export async function listOrgMemberships(email) {
384
- return loadMemberships(email);
443
+ const memberships = await loadMemberships(email);
444
+ return scopeMembershipsForDelegatedIdentity(email, memberships);
385
445
  }
386
446
  export async function listOrgMembershipsForEvent(event, email, selectedOrgId) {
387
447
  const memberships = await loadMembershipsForEvent(event, email);
@@ -423,6 +483,15 @@ async function loadMembershipsUncached(email) {
423
483
  }) ?? null);
424
484
  }
425
485
  export async function resolveOrgScopeForEmail(email) {
486
+ const delegatedOrgId = delegatedOrgMemberScope(email);
487
+ if (delegatedOrgId !== undefined) {
488
+ if (!delegatedOrgId)
489
+ return { status: "unknown" };
490
+ const role = await readOrgMemberRole(delegatedOrgId, email);
491
+ return role
492
+ ? { status: "organization", orgId: delegatedOrgId }
493
+ : { status: "unknown" };
494
+ }
426
495
  if (getRequestContext()?.orgScope === "personal") {
427
496
  return { status: "personal" };
428
497
  }
@@ -457,6 +526,13 @@ export async function resolveActiveOrgIdForStorageCredential(email) {
457
526
  const normalizedEmail = email.trim().toLowerCase();
458
527
  if (!normalizedEmail)
459
528
  return null;
529
+ const delegatedOrgId = delegatedOrgMemberScope(normalizedEmail);
530
+ if (delegatedOrgId !== undefined) {
531
+ if (!delegatedOrgId)
532
+ return null;
533
+ const role = await readOrgMemberRole(delegatedOrgId, normalizedEmail);
534
+ return role ? delegatedOrgId : null;
535
+ }
460
536
  const { rows } = await getDbExec().execute({
461
537
  sql: `SELECT m.org_id AS "orgId"
462
538
  FROM org_members m
@@ -476,6 +552,22 @@ export async function resolveActiveOrgIdForStorageCredential(email) {
476
552
  return rows.some((row) => row.orgId === activeOrgId) ? activeOrgId : null;
477
553
  }
478
554
  export async function resolveOrgIdForEmailViaEvent(event, email) {
555
+ const delegatedOrgId = delegatedOrgMemberScope(email);
556
+ if (delegatedOrgId !== undefined) {
557
+ if (!delegatedOrgId)
558
+ return null;
559
+ const memberships = await loadMembershipsForEvent(event, email);
560
+ if (!memberships || !memberships.some((m) => m.orgId === delegatedOrgId)) {
561
+ return null;
562
+ }
563
+ const refreshed = await refreshFederatedMemberships(event, email, memberships, delegatedOrgId);
564
+ if (refreshed !== memberships) {
565
+ updateMembershipsForEvent(event, email, refreshed);
566
+ }
567
+ return refreshed.some((m) => m.orgId === delegatedOrgId)
568
+ ? delegatedOrgId
569
+ : null;
570
+ }
479
571
  if (getRequestContext()?.orgScope === "personal")
480
572
  return null;
481
573
  const settingPromise = loadActiveOrgSettingForEvent(event, email);
@@ -842,7 +934,8 @@ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
842
934
  if (!normalizedDomain)
843
935
  return null;
844
936
  const { rows } = await getDbExec().execute({
845
- sql: `SELECT id, allowed_domain, a2a_secret
937
+ sql: `SELECT id, allowed_domain, a2a_secret,
938
+ a2a_member_delegation_enabled
846
939
  FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
847
940
  args: [normalizedDomain],
848
941
  });
@@ -858,7 +951,12 @@ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
858
951
  const secret = String(row.a2a_secret ?? "").trim();
859
952
  if (!orgId || orgDomain !== normalizedDomain || !secret)
860
953
  return null;
861
- return { orgId, orgDomain, secret };
954
+ return {
955
+ orgId,
956
+ orgDomain,
957
+ secret,
958
+ a2aMemberDelegationEnabled: row.a2a_member_delegation_enabled === true,
959
+ };
862
960
  }
863
961
  export async function isSoleOrgDomain(domain) {
864
962
  try {
@@ -52,7 +52,7 @@ export declare function validateFederatedOrganizationMembership(event: H3Event |
52
52
  export declare function validateFederatedOrganizationMembershipForCurrentRequest(input: {
53
53
  orgId: string;
54
54
  email: string;
55
- }): Promise<FederatedMembershipValidation>;
55
+ }, event?: H3Event): Promise<FederatedMembershipValidation>;
56
56
  export declare function syncOrganizationToIdentityHub(event: H3Event, input: FederatedOrganizationSyncInput): Promise<boolean>;
57
57
  export declare function provisionFederatedOrganization(identity: FederatedOrganizationIdentity, options?: {
58
58
  /** The signing-in caller's request; see `setActiveOrgId`. */
@@ -408,8 +408,8 @@ export async function validateFederatedOrganizationMembership(event, input) {
408
408
  }
409
409
  return { active: true, role: body.memberRole };
410
410
  }
411
- export async function validateFederatedOrganizationMembershipForCurrentRequest(input) {
412
- return validateFederatedOrganizationMembership(requestEventFromContext(), input);
411
+ export async function validateFederatedOrganizationMembershipForCurrentRequest(input, event) {
412
+ return validateFederatedOrganizationMembership(event ?? requestEventFromContext(), input);
413
413
  }
414
414
  export async function syncOrganizationToIdentityHub(event, input) {
415
415
  const rollout = await federationRolloutState(input.email, input.id);