@agent-native/core 0.212.1 → 0.213.1

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 (84) 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/audit/actions/export-audit-events.js +1 -0
  16. package/dist/audit/attribution.d.ts +5 -4
  17. package/dist/audit/attribution.js +2 -1
  18. package/dist/audit/index.d.ts +1 -1
  19. package/dist/audit/ocsf.js +1 -0
  20. package/dist/audit/read-scope.js +2 -12
  21. package/dist/audit/record.js +1 -0
  22. package/dist/audit/store.js +12 -3
  23. package/dist/audit/types.d.ts +4 -0
  24. package/dist/automation/index.d.ts +1 -1
  25. package/dist/automations/service.js +3 -10
  26. package/dist/client/org/hooks.d.ts +3 -0
  27. package/dist/client/org/hooks.js +12 -0
  28. package/dist/connections/catalog.d.ts +1 -1
  29. package/dist/connections/catalog.js +7 -1
  30. package/dist/credentials/index.js +18 -2
  31. package/dist/db/schema-snapshot.generated.js +5 -2
  32. package/dist/feature-flags/a2a-action-route.js +1 -0
  33. package/dist/file-upload/storage-settings.js +2 -9
  34. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  35. package/dist/jobs/tools.js +2 -12
  36. package/dist/mcp/actions/service-token-access.js +15 -11
  37. package/dist/mcp/credential-issuance.js +9 -2
  38. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  39. package/dist/notifications/routes.d.ts +3 -3
  40. package/dist/observability/routes.d.ts +1 -1
  41. package/dist/org/actions.d.ts +1 -1
  42. package/dist/org/actions.js +3 -7
  43. package/dist/org/app-roles.js +10 -1
  44. package/dist/org/context.d.ts +1 -0
  45. package/dist/org/context.js +122 -24
  46. package/dist/org/federation.d.ts +1 -1
  47. package/dist/org/federation.js +2 -2
  48. package/dist/org/handlers.d.ts +4 -0
  49. package/dist/org/handlers.js +140 -44
  50. package/dist/org/index.d.ts +2 -1
  51. package/dist/org/membership.d.ts +6 -4
  52. package/dist/org/membership.js +7 -5
  53. package/dist/org/migrations.js +6 -0
  54. package/dist/org/plugin.d.ts +1 -0
  55. package/dist/org/plugin.js +9 -1
  56. package/dist/org/private-icon-handlers.js +4 -10
  57. package/dist/org/types.d.ts +2 -0
  58. package/dist/org/workspace-app-access.js +62 -14
  59. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  60. package/dist/provider-api/actions/provider-api.d.ts +3 -3
  61. package/dist/secrets/routes.d.ts +10 -10
  62. package/dist/server/action-routes.d.ts +1 -0
  63. package/dist/server/action-routes.js +13 -1
  64. package/dist/server/agent-chat/prompt-resources.js +1 -1
  65. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  66. package/dist/server/index.d.ts +1 -0
  67. package/dist/server/index.js +1 -0
  68. package/dist/server/org-admin.js +3 -2
  69. package/dist/server/personal-provider-key-holders.d.ts +1 -1
  70. package/dist/server/personal-provider-key-holders.js +7 -2
  71. package/dist/server/personal-provider-key-policy.d.ts +2 -0
  72. package/dist/server/personal-provider-key-policy.js +21 -5
  73. package/dist/server/request-context.d.ts +2 -0
  74. package/dist/server/request-context.js +7 -0
  75. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  76. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
  77. package/dist/triggers/actions/manage-automation.d.ts +5 -5
  78. package/dist/triggers/routes.js +2 -9
  79. package/dist/usage/alerts-store.js +2 -9
  80. package/dist/usage/metrics-store.js +2 -9
  81. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
  82. package/dist/workspace-connections/groups.js +2 -11
  83. package/package.json +3 -3
  84. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
@@ -183,6 +183,7 @@ async function verifyContinuationToken(token, context, options) {
183
183
  verified = await verifyA2AToken(token, context.event, {
184
184
  includeClaims: true,
185
185
  globalSecretOnly: true,
186
+ allowAnthropicManagedContinuation: true,
186
187
  });
187
188
  }
188
189
  catch (cause) {
@@ -1,8 +1,9 @@
1
- import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
- import { getGlobalA2ASecret, signA2AOrganizationToken, signA2AToken, } from "./client.js";
1
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
+ import { getGlobalA2ASecret, signA2AOrgMemberDelegationToken, signA2AOrganizationToken, signA2AToken, } from "./client.js";
3
3
  const DEFAULT_A2A_CALLER_TOKEN_TTL = "5m";
4
4
  export async function resolveA2ACallerAuth(options) {
5
5
  const userEmail = getRequestUserEmail();
6
+ const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
6
7
  const globalSecret = getGlobalA2ASecret();
7
8
  const metadata = {};
8
9
  if (userEmail)
@@ -24,6 +25,7 @@ export async function resolveA2ACallerAuth(options) {
24
25
  apiKeyAttempts.push(token);
25
26
  };
26
27
  if (userEmail &&
28
+ !isOrgMemberDelegation &&
27
29
  options?.audience &&
28
30
  globalSecret &&
29
31
  (options?.userIdentityOnly || !orgId || orgDomain?.trim())) {
@@ -40,6 +42,9 @@ export async function resolveA2ACallerAuth(options) {
40
42
  orgDomain &&
41
43
  options?.audience &&
42
44
  (orgSecret || globalSecret)) {
45
+ if (userEmail && orgSecret) {
46
+ addApiKeyAttempt(await signA2AOrgMemberDelegationToken(userEmail, orgDomain, orgSecret, options.audience));
47
+ }
43
48
  addApiKeyAttempt(await signA2AOrganizationToken(orgDomain, orgSecret, undefined, {
44
49
  expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
45
50
  audience: options?.audience,
@@ -64,6 +64,7 @@ export declare function signA2AOrganizationToken(orgDomain: string | undefined,
64
64
  preferGlobalSecret?: boolean;
65
65
  extraClaims?: Record<string, unknown>;
66
66
  }): Promise<string>;
67
+ export declare function signA2AOrgMemberDelegationToken(email: string, orgDomain: string, orgSecret: string, audience: string | string[]): Promise<string>;
67
68
  export declare function shouldPreferGlobalA2ASecret(orgSecret?: string): boolean;
68
69
  export declare class A2AClient {
69
70
  private baseUrl;
@@ -8,6 +8,7 @@ import { getRequestContext } from "../server/request-context.js";
8
8
  import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
9
9
  import { canonicalA2AAudience } from "./audience.js";
10
10
  import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
11
+ import { A2A_ORG_MEMBER_DELEGATION_TYPE } from "./organization-identity.js";
11
12
  import { RemoteAgentCredentialRejectedError } from "./remote-agent-auth.js";
12
13
  import { workspacePrivateOrigins } from "./workspace-private-origins.js";
13
14
  const DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS = 15_000;
@@ -172,6 +173,27 @@ export async function signA2AOrganizationToken(orgDomain, orgSecret, orgId, opti
172
173
  jwt.setAudience(options.audience);
173
174
  return jwt.sign(new TextEncoder().encode(secret));
174
175
  }
176
+ export async function signA2AOrgMemberDelegationToken(email, orgDomain, orgSecret, audience) {
177
+ const normalizedEmail = email.trim().toLowerCase();
178
+ const normalizedDomain = orgDomain.trim().toLowerCase();
179
+ const secret = orgSecret.trim();
180
+ if (!normalizedEmail || !normalizedDomain || !secret || !audience) {
181
+ throw new Error("A member email, organization domain, organization secret, and audience are required.");
182
+ }
183
+ const appUrl = getAppConfig().app.url ?? "http://localhost:3000";
184
+ return new jose.SignJWT({
185
+ typ: A2A_ORG_MEMBER_DELEGATION_TYPE,
186
+ sub: normalizedEmail,
187
+ org_domain: normalizedDomain,
188
+ })
189
+ .setProtectedHeader({ alg: "HS256" })
190
+ .setIssuer(appUrl)
191
+ .setIssuedAt()
192
+ .setJti(randomUUID())
193
+ .setExpirationTime("5m")
194
+ .setAudience(audience)
195
+ .sign(new TextEncoder().encode(secret));
196
+ }
175
197
  export function shouldPreferGlobalA2ASecret(orgSecret) {
176
198
  return !!getGlobalA2ASecret() || !orgSecret;
177
199
  }
@@ -1517,7 +1539,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
1517
1539
  attempts.push(token);
1518
1540
  };
1519
1541
  const globalSecret = getGlobalA2ASecret();
1542
+ const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
1520
1543
  if (opts?.userEmail &&
1544
+ !isOrgMemberDelegation &&
1521
1545
  globalSecret &&
1522
1546
  (!opts.orgId || opts.orgDomain?.trim())) {
1523
1547
  add(await signA2AToken(opts.userEmail, opts.orgDomain, undefined, {
@@ -1525,6 +1549,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
1525
1549
  audience,
1526
1550
  }));
1527
1551
  }
1552
+ if (opts?.userEmail && opts.orgDomain && opts.orgSecret && audience) {
1553
+ add(await signA2AOrgMemberDelegationToken(opts.userEmail, opts.orgDomain, opts.orgSecret, audience));
1554
+ }
1528
1555
  if (opts?.orgDomain && (opts.orgSecret || globalSecret)) {
1529
1556
  add(await signA2AOrganizationToken(opts.orgDomain, opts.orgSecret, undefined, { audience }));
1530
1557
  }
@@ -302,9 +302,11 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
302
302
  const verifiedEmail = processorMeta.verifiedEmail;
303
303
  const identityAssurance = processorMeta.identityAssurance === "organization"
304
304
  ? "organization"
305
- : processorMeta.identityAssurance === "user"
306
- ? "user"
307
- : undefined;
305
+ : processorMeta.identityAssurance === "org-member"
306
+ ? "org-member"
307
+ : processorMeta.identityAssurance === "user"
308
+ ? "user"
309
+ : undefined;
308
310
  const orgDomainHint = processorMeta.orgDomainHint;
309
311
  const verifiedOrgId = typeof processorMeta.verifiedOrgId === "string"
310
312
  ? processorMeta.verifiedOrgId.trim()
@@ -367,6 +369,9 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
367
369
  const { runWithRequestContext } = await import("../server/request-context.js");
368
370
  await runWithRequestContext({
369
371
  userEmail: verifiedEmail,
372
+ ...(identityAssurance
373
+ ? { a2aIdentityAssurance: identityAssurance }
374
+ : {}),
370
375
  ...(resolvedOrgId
371
376
  ? { orgId: resolvedOrgId }
372
377
  : verifiedEmail
@@ -547,12 +552,19 @@ async function withA2ARequestContext(metadata, event, fn) {
547
552
  const { runWithRequestContext } = await import("../server/request-context.js");
548
553
  const verifiedEmail = event?.context?.__a2aVerifiedEmail ?? undefined;
549
554
  const verifiedOrgId = event?.context?.__a2aVerifiedOrgId ?? undefined;
555
+ const rawIdentityAssurance = event?.context?.__a2aIdentityAssurance;
556
+ const identityAssurance = rawIdentityAssurance === "user" ||
557
+ rawIdentityAssurance === "organization" ||
558
+ rawIdentityAssurance === "org-member"
559
+ ? rawIdentityAssurance
560
+ : undefined;
550
561
  const requestOrigin = requestOriginForContext(metadata, event);
551
562
  if (event?.context && verifiedEmail && !verifiedOrgId) {
552
563
  markExplicitPersonalOrgScope(event);
553
564
  }
554
565
  return runWithRequestContext({
555
566
  userEmail: verifiedEmail,
567
+ ...(identityAssurance ? { a2aIdentityAssurance: identityAssurance } : {}),
556
568
  ...(verifiedOrgId
557
569
  ? { orgId: verifiedOrgId }
558
570
  : verifiedEmail
@@ -1,4 +1,5 @@
1
1
  import type { JWTPayload } from "jose";
2
+ export declare const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
2
3
  export interface VerifiedA2AOrganizationIdentity {
3
4
  orgId: string;
4
5
  orgDomain: string | null;
@@ -6,10 +7,12 @@ export interface VerifiedA2AOrganizationIdentity {
6
7
  export interface A2AOrganizationCredential extends VerifiedA2AOrganizationIdentity {
7
8
  orgDomain: string;
8
9
  secret: string;
10
+ a2aMemberDelegationEnabled?: boolean;
9
11
  }
10
12
  /**
11
13
  * An organization secret authenticates an organization principal. Its JWT
12
- * subject is caller-controlled data and must never become a user identity.
14
+ * subject is caller-controlled data unless it is an opted-in, audience-bound
15
+ * member delegation verified against the receiver's current roster.
13
16
  */
14
17
  export declare function organizationPrincipalClaims(payload: JWTPayload, organization: A2AOrganizationCredential): JWTPayload | null;
15
18
  /**
@@ -1,6 +1,8 @@
1
+ export const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
1
2
  /**
2
3
  * An organization secret authenticates an organization principal. Its JWT
3
- * subject is caller-controlled data and must never become a user identity.
4
+ * subject is caller-controlled data unless it is an opted-in, audience-bound
5
+ * member delegation verified against the receiver's current roster.
4
6
  */
5
7
  export function organizationPrincipalClaims(payload, organization) {
6
8
  const orgDomain = typeof payload.org_domain === "string"
@@ -2,16 +2,15 @@ import * as jose from "jose";
2
2
  import "../authorization/check-action.js";
3
3
  import type { A2AConfig, AgentSkill } from "./types.js";
4
4
  /**
5
- * Result of verifying an inbound A2A JWT. `email` is present only when a
6
- * deployment-secret user assertion verifies; org-secret tokens carry an
7
- * organization principal and never supply a user identity. `orgDomain` and
8
- * `orgId` are bound to local organization metadata when present.
5
+ * Result of verifying an inbound A2A JWT. `email` is present for deployment
6
+ * user assertions or an explicitly opted-in org-member delegation. `orgDomain`
7
+ * and `orgId` are bound to local organization metadata when present.
9
8
  */
10
9
  export interface A2ATokenPayload {
11
10
  email: string | null;
12
11
  orgDomain: string | null;
13
12
  orgId?: string;
14
- identityAssurance?: "user" | "organization";
13
+ identityAssurance?: "user" | "organization" | "org-member";
15
14
  claims?: jose.JWTPayload;
16
15
  }
17
16
  export declare class A2AIdentityVerificationUnavailableError extends Error {
@@ -47,6 +46,8 @@ export declare function verifyA2AToken(token: string, event?: any, audienceOptio
47
46
  globalSecretOnly?: boolean;
48
47
  /** Only use this explicitly configured credential; never fall back. */
49
48
  verificationSecret?: string;
49
+ /** Dedicated verifier for the typed Anthropic continuation credential. */
50
+ allowAnthropicManagedContinuation?: boolean;
50
51
  requireAudience?: boolean;
51
52
  }): Promise<A2ATokenPayload>;
52
53
  export type A2ATokenFailureCause = "malformed" | "secret-missing" | "audience-unresolvable" | "audience" | "issuer" | "expired" | "signature" | "invalid-claims" | "mcp-credential" | "missing-subject" | "org-binding" | "org-lookup-failed" | "undetermined";
@@ -19,7 +19,7 @@ import { addAgentProfileExamples } from "./app-profile.js";
19
19
  import { canonicalA2AAudience } from "./audience.js";
20
20
  import { hasConfiguredA2ASecret, isA2AProductionRuntime, } from "./auth-policy.js";
21
21
  import { handleJsonRpcH3, processA2ATaskFromQueue } from "./handlers.js";
22
- import { organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
22
+ import { A2A_ORG_MEMBER_DELEGATION_TYPE, organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
23
23
  import { claimA2AApproval, a2aTaskLeaseThreadId, getA2AApprovalForOwner, settleA2AApproval, } from "./task-store.js";
24
24
  let _warnedUnauthA2A = false;
25
25
  function warnA2AUnauthOnce() {
@@ -59,7 +59,7 @@ export function isA2AIdentityVerificationUnavailableError(error) {
59
59
  (error instanceof Error &&
60
60
  error.name === "A2AIdentityVerificationUnavailableError"));
61
61
  }
62
- function identityPayload(payload, audienceOptions, verifiedOrganization) {
62
+ function identityPayload(payload, audienceOptions, verifiedOrganization, identityAssurance) {
63
63
  const email = typeof payload.sub === "string" && payload.sub.trim()
64
64
  ? payload.sub.trim()
65
65
  : null;
@@ -79,9 +79,34 @@ function identityPayload(payload, audienceOptions, verifiedOrganization) {
79
79
  ...(!email && verifiedOrganization
80
80
  ? { identityAssurance: "organization" }
81
81
  : {}),
82
+ ...(identityAssurance ? { identityAssurance } : {}),
82
83
  ...(audienceOptions?.includeClaims ? { claims } : {}),
83
84
  };
84
85
  }
86
+ function validOrgMemberDelegationClaims(payload) {
87
+ const email = typeof payload.sub === "string" ? payload.sub.trim() : "";
88
+ const jti = typeof payload.jti === "string" ? payload.jti.trim() : "";
89
+ const issuedAt = payload.iat;
90
+ const expiresAt = payload.exp;
91
+ const now = Math.floor(Date.now() / 1_000);
92
+ const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
93
+ return (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE &&
94
+ email.length <= 320 &&
95
+ email.includes("@") &&
96
+ !/[\u0000-\u001f\u007f]/.test(email) &&
97
+ jti.length > 0 &&
98
+ jti.length <= 128 &&
99
+ !/[\u0000-\u001f\u007f]/.test(jti) &&
100
+ typeof issuedAt === "number" &&
101
+ Number.isSafeInteger(issuedAt) &&
102
+ typeof expiresAt === "number" &&
103
+ Number.isSafeInteger(expiresAt) &&
104
+ issuedAt <= now + 60 &&
105
+ expiresAt > now &&
106
+ expiresAt > issuedAt &&
107
+ expiresAt - issuedAt <= 15 * 60 &&
108
+ audiences.some((audience) => typeof audience === "string" && audience.trim()));
109
+ }
85
110
  async function verifyJwtWithSecret(token, secret, options) {
86
111
  try {
87
112
  const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), options);
@@ -327,6 +352,17 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
327
352
  if (globalSecret) {
328
353
  const payload = await verifyJwtWithSecret(token, globalSecret, verifyOptions);
329
354
  if (payload) {
355
+ // Typed credentials have their own verifier. Only the managed-agent
356
+ // confirmation verifier may accept its continuation token here.
357
+ if (typeof payload.typ !== "undefined") {
358
+ const managedContinuation = payload.typ === "anthropic-managed-continuation" &&
359
+ audienceOptions?.globalSecretOnly === true &&
360
+ audienceOptions.allowAnthropicManagedContinuation === true &&
361
+ audienceOptions.includeClaims === true;
362
+ if (!managedContinuation) {
363
+ return { payload: { email: null, orgDomain: null } };
364
+ }
365
+ }
330
366
  if (typeof payload.org_domain === "string" && payload.org_domain.trim()) {
331
367
  let organization;
332
368
  try {
@@ -388,12 +424,49 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
388
424
  }
389
425
  if (!organization)
390
426
  return { payload: { email: null, orgDomain: null } };
391
- const payload = await verifyJwtWithSecret(token, organization.secret, verifyOptions);
427
+ const orgVerifyOptions = unverifiedPayload?.typ === A2A_ORG_MEMBER_DELEGATION_TYPE
428
+ ? { ...verifyOptions, algorithms: ["HS256"] }
429
+ : verifyOptions;
430
+ const payload = await verifyJwtWithSecret(token, organization.secret, orgVerifyOptions);
392
431
  if (!payload)
393
432
  return { payload: { email: null, orgDomain: null } };
394
433
  const claims = organizationPrincipalClaims(payload, organization);
395
434
  if (!claims)
396
435
  return { payload: { email: null, orgDomain: null } };
436
+ if (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE) {
437
+ if (!validOrgMemberDelegationClaims(payload)) {
438
+ return { payload: { email: null, orgDomain: null } };
439
+ }
440
+ if (organization.secret.trim() === globalSecret) {
441
+ return { payload: { email: null, orgDomain: null } };
442
+ }
443
+ let isCurrentMember = false;
444
+ if (organization.a2aMemberDelegationEnabled) {
445
+ try {
446
+ const { isOrgMemberForA2A } = await import("../org/membership.js");
447
+ isCurrentMember = await isOrgMemberForA2A(organization.orgId, String(payload.sub).trim(), event);
448
+ }
449
+ catch (cause) {
450
+ throw new A2AIdentityVerificationUnavailableError(cause);
451
+ }
452
+ }
453
+ if (isCurrentMember) {
454
+ return {
455
+ payload: identityPayload(payload, audienceOptions, organization, "org-member"),
456
+ verifiedOrgId: organization.orgId,
457
+ };
458
+ }
459
+ return {
460
+ payload: {
461
+ email: null,
462
+ orgDomain: organization.orgDomain,
463
+ orgId: organization.orgId,
464
+ identityAssurance: "organization",
465
+ ...(audienceOptions?.includeClaims ? { claims } : {}),
466
+ },
467
+ verifiedOrgId: organization.orgId,
468
+ };
469
+ }
397
470
  warnUserAssertionDemotedOnce(organization.orgId, payload, "user token verified only with org secret");
398
471
  return {
399
472
  payload: {
@@ -742,9 +815,9 @@ export function mountA2A(nitroApp, config, routePrefix = "/_agent-native") {
742
815
  }
743
816
  verifiedCallerEmail = tokenPayload.email;
744
817
  verifiedOrgDomain = tokenPayload.orgDomain;
745
- verifiedIdentityAssurance = tokenPayload.email
746
- ? "user"
747
- : tokenPayload.identityAssurance;
818
+ verifiedIdentityAssurance =
819
+ tokenPayload.identityAssurance ??
820
+ (tokenPayload.email ? "user" : undefined);
748
821
  bearerTokenVerified = verifiedIdentityAssurance !== undefined;
749
822
  verifiedAudienceBound =
750
823
  bearerTokenVerified && tokenHasAudienceClaim(bearerToken);
@@ -3,6 +3,7 @@ export interface VerifiedA2AClaims {
3
3
  orgId: string;
4
4
  orgDomain: string;
5
5
  jti: string;
6
+ identityAssurance?: "user" | "org-member";
6
7
  issuer?: string;
7
8
  scope: string[];
8
9
  }
@@ -36,6 +36,7 @@ export async function verifyA2ATokenWithClaims(token, event) {
36
36
  orgId,
37
37
  orgDomain,
38
38
  jti,
39
+ identityAssurance: identity.identityAssurance === "org-member" ? "org-member" : "user",
39
40
  ...(issuer ? { issuer } : {}),
40
41
  scope: scopes,
41
42
  }
@@ -92,7 +92,7 @@ export default defineAction({
92
92
  return {
93
93
  ...policy,
94
94
  canManage,
95
- affectedMembers: await listPersonalProviderKeyHolders(orgId),
95
+ affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
96
96
  changed,
97
97
  };
98
98
  }
@@ -101,7 +101,9 @@ export default defineAction({
101
101
  ...policy,
102
102
  canManage,
103
103
  ...(canManage
104
- ? { affectedMembers: await listPersonalProviderKeyHolders(orgId) }
104
+ ? {
105
+ affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
106
+ }
105
107
  : {}),
106
108
  };
107
109
  },
@@ -1,4 +1,4 @@
1
- import { getDbExec } from "../db/client.js";
1
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
2
2
  import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
3
3
  import { deleteOrgSetting, deleteUserSetting, getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "../settings/index.js";
4
4
  export const AGENT_APP_MODEL_DEFAULT_KEY_PREFIX = "agent-app-model-default";
@@ -116,15 +116,7 @@ export async function canUpdateAgentAppModelDefaultSettings(userEmail, orgId, or
116
116
  if (!orgId)
117
117
  return orgScope === "personal";
118
118
  try {
119
- const exec = getDbExec();
120
- const { rows } = await exec.execute({
121
- sql: `SELECT role FROM org_members
122
- WHERE org_id = ? AND LOWER(email) = ?
123
- AND federation_removal_pending_at IS NULL
124
- LIMIT 1`,
125
- args: [orgId, userEmail.toLowerCase()],
126
- });
127
- const role = String(rows[0]?.role ?? "");
119
+ const role = await readOrgMemberRole(orgId, userEmail);
128
120
  return role === "owner" || role === "admin";
129
121
  }
130
122
  catch {
@@ -1,4 +1,4 @@
1
- import { getDbExec } from "../db/client.js";
1
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
2
2
  import { getOrgSetting, putOrgSetting, deleteOrgSetting, getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/index.js";
3
3
  export const AGENT_LOOP_SETTINGS_KEY = "agent-loop";
4
4
  export const DEFAULT_AGENT_MAX_ITERATIONS = 400;
@@ -169,15 +169,7 @@ export async function canUpdateAgentLoopSettings(userEmail, orgId) {
169
169
  if (!orgId)
170
170
  return true;
171
171
  try {
172
- const exec = getDbExec();
173
- const { rows } = await exec.execute({
174
- sql: `SELECT role FROM org_members
175
- WHERE org_id = ? AND LOWER(email) = ?
176
- AND federation_removal_pending_at IS NULL
177
- LIMIT 1`,
178
- args: [orgId, userEmail.toLowerCase()],
179
- });
180
- const role = String(rows[0]?.role ?? "");
172
+ const role = await readOrgMemberRole(orgId, userEmail);
181
173
  return role === "owner" || role === "admin";
182
174
  }
183
175
  catch {
@@ -26,6 +26,7 @@ const CSV_COLUMNS = [
26
26
  ["reportedClientVersion", "reported_client_version"],
27
27
  ["oauthClientId", "oauth_client_id"],
28
28
  ["approval", "approval"],
29
+ ["identityAssurance", "identity_assurance"],
29
30
  ];
30
31
  function csvField(value) {
31
32
  if (value === null || value === undefined)
@@ -1,4 +1,4 @@
1
- import type { AuditApproval } from "./types.js";
1
+ import type { AuditApproval, AuditIdentityAssurance } from "./types.js";
2
2
  declare const auditAttributionBrand: unique symbol;
3
3
  interface AuditAttributionFields {
4
4
  caller: string;
@@ -16,11 +16,12 @@ interface AuditAttributionFields {
16
16
  };
17
17
  mcpClientAccepted?: true;
18
18
  approval?: AuditApproval;
19
+ identityAssurance?: AuditIdentityAssurance;
19
20
  }
20
21
  /**
21
22
  * Where an audited call came from and what vouched for it: its source, the
22
- * MCP client it reported, the OAuth client of its credential, the approval
23
- * signal, and its run and network ids. `recordActionAudit` requires one and
23
+ * MCP client it reported, the OAuth client of its credential, verified A2A
24
+ * identity assurance, the approval signal, and its run and network ids. `recordActionAudit` requires one and
24
25
  * only {@link auditAttribution} makes one, so a recorder cannot build its own
25
26
  * context and store nulls for a field it forgot.
26
27
  */
@@ -38,7 +39,7 @@ export type AuditOverride = Partial<Pick<AuditAttributionFields, "threadId" | "t
38
39
  * The run context attribution reads from; an `ActionRunContext` is one.
39
40
  * Approval is not read from it: a context can be handed to another action.
40
41
  */
41
- export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted">>;
42
+ export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted" | "identityAssurance">>;
42
43
  /** A recorder was given no attribution, or one `auditAttribution` did not make. */
43
44
  export declare class AuditAttributionError extends Error {
44
45
  constructor(recorder: string);
@@ -1,5 +1,5 @@
1
1
  import { currentInvocationEvidence } from "../action-frame.js";
2
- import { getVerifiedMcpCredentialFromEvent } from "../server/request-context.js";
2
+ import { getRequestContext, getVerifiedMcpCredentialFromEvent, } from "../server/request-context.js";
3
3
  // Registered, so an attribution made by another loaded copy of this module
4
4
  // still passes the check.
5
5
  const auditAttributionBrand = Symbol.for("@agent-native/core/audit-attribution");
@@ -40,6 +40,7 @@ export function auditAttribution(ctx) {
40
40
  mcpClient: ctx?.mcpClient,
41
41
  mcpClientAccepted: evidence?.mcpClientAccepted,
42
42
  approval: override?.approval,
43
+ identityAssurance: getRequestContext()?.a2aIdentityAssurance,
43
44
  };
44
45
  return { ...fields, [auditAttributionBrand]: true };
45
46
  }
@@ -1,4 +1,4 @@
1
- export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
1
+ export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditIdentityAssurance, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
2
2
  export { AGENT_AUDIT_CALLERS, deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
3
3
  export { redactArgsToJson } from "./redact.js";
4
4
  export { ensureAuditTables, insertAuditEvent, queryAuditEvents, queryAuditEventPage, queryAuditApps, getAuditEventById, deleteOldAuditEvents, type AuditEventPage, type AuditReadScope, type AuditTrail, } from "./store.js";
@@ -177,6 +177,7 @@ export function auditEventToOcsf(event) {
177
177
  keep("reported_client_version", event.reportedClientVersion);
178
178
  keep("oauth_client_id", event.oauthClientId);
179
179
  keep("approval", event.approval);
180
+ keep("identity_assurance", event.identityAssurance);
180
181
  const resource = event.targetType || event.targetId
181
182
  ? [
182
183
  {
@@ -4,9 +4,9 @@
4
4
  * decided on the server from `org_members`, never from anything the caller
5
5
  * sends.
6
6
  */
7
- import { getDbExec } from "../db/client.js";
8
7
  import { isMissingOrganizationTableError } from "../org/membership.js";
9
8
  import { canManageOrg } from "../org/permissions.js";
9
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
10
10
  export class AuditAccessError extends Error {
11
11
  statusCode = 403;
12
12
  constructor(message) {
@@ -16,17 +16,7 @@ export class AuditAccessError extends Error {
16
16
  }
17
17
  async function readOrgRole(orgId, email) {
18
18
  try {
19
- const { rows } = await getDbExec().execute({
20
- sql: `SELECT role FROM org_members
21
- WHERE org_id = ? AND LOWER(email) = ?
22
- AND federation_removal_pending_at IS NULL
23
- LIMIT 1`,
24
- args: [orgId, email.toLowerCase()],
25
- });
26
- const role = rows[0]?.role;
27
- return role === "owner" || role === "admin" || role === "member"
28
- ? role
29
- : null;
19
+ return await readOrgMemberRole(orgId, email);
30
20
  }
31
21
  catch (error) {
32
22
  // No org tables means no organization to administer. Any other failure
@@ -170,6 +170,7 @@ export async function recordActionAudit(input) {
170
170
  reportedClientVersion: reportedLabel(attribution.mcpClient?.version),
171
171
  oauthClientId: attribution.mcpClient?.oauthClientId ?? null,
172
172
  approval: approvalEvidence(attribution),
173
+ identityAssurance: attribution.identityAssurance ?? null,
173
174
  };
174
175
  if (integration) {
175
176
  if (ctx?.orgId &&
@@ -78,6 +78,12 @@ export const auditStore = defineStore({
78
78
  "ALTER TABLE agent_audit_log ADD COLUMN IF NOT EXISTS approval TEXT",
79
79
  ],
80
80
  },
81
+ {
82
+ name: "a2a-identity-assurance",
83
+ sql: [
84
+ "ALTER TABLE agent_audit_log ADD COLUMN IF NOT EXISTS identity_assurance TEXT",
85
+ ],
86
+ },
81
87
  ],
82
88
  });
83
89
  export function ensureAuditTables() {
@@ -100,8 +106,8 @@ export async function insertAuditEvent(event) {
100
106
  error_code, owner_email, visibility, run_id, task_id, parent_task_id,
101
107
  source_kind, source_platform, source_id, source_url, network_protocol,
102
108
  network_id, network_peer, app, reported_client_name,
103
- reported_client_version, oauth_client_id, approval)
104
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
109
+ reported_client_version, oauth_client_id, approval, identity_assurance)
110
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
105
111
  args: [
106
112
  event.id,
107
113
  event.createdAt,
@@ -135,6 +141,7 @@ export async function insertAuditEvent(event) {
135
141
  event.reportedClientVersion ?? null,
136
142
  event.oauthClientId ?? null,
137
143
  event.approval ?? null,
144
+ event.identityAssurance ?? null,
138
145
  ].map(storableText),
139
146
  });
140
147
  }
@@ -203,6 +210,8 @@ function mapRow(row) {
203
210
  reportedClientVersion: row.reported_client_version ?? null,
204
211
  oauthClientId: row.oauth_client_id ?? null,
205
212
  approval: (row.approval ?? null),
213
+ identityAssurance: (row.identity_assurance ??
214
+ null),
206
215
  };
207
216
  }
208
217
  function sharedVisibilities(scope) {
@@ -252,7 +261,7 @@ const LIST_COLUMNS = "id, created_at, action, caller, actor_kind, actor_email, o
252
261
  "error_code, owner_email, visibility, app, run_id, task_id, " +
253
262
  "parent_task_id, source_kind, source_platform, source_id, source_url, " +
254
263
  "network_protocol, network_id, network_peer, reported_client_name, " +
255
- "reported_client_version, oauth_client_id, approval";
264
+ "reported_client_version, oauth_client_id, approval, identity_assurance";
256
265
  function clampLimit(limit) {
257
266
  return Math.min(Math.max(1, Math.floor(limit ?? DEFAULT_LIMIT)), MAX_LIMIT);
258
267
  }
@@ -7,6 +7,8 @@ export type AuditActorKind = "agent" | "human" | "service" | "system";
7
7
  * accepted MCP elicitation response; the server cannot verify who responded.
8
8
  */
9
9
  export type AuditApproval = "agent-chat" | "a2a-review" | "mcp-client-accepted";
10
+ /** Verified identity provenance for a request authenticated through A2A. */
11
+ export type AuditIdentityAssurance = "user" | "organization" | "org-member";
10
12
  /**
11
13
  * Who can read an event besides its owner.
12
14
  * - `private` — the owner only (the default; personal content stays here).
@@ -80,6 +82,8 @@ export interface AuditEvent {
80
82
  * not verify who responded. Rows written before the column existed are null.
81
83
  */
82
84
  approval?: AuditApproval | null;
85
+ /** Verified identity provenance; null on older rows or non-A2A calls. */
86
+ identityAssurance?: AuditIdentityAssurance | null;
83
87
  }
84
88
  export interface AuditQueryFilters {
85
89
  targetType?: string;
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
139
139
  duplicate?: undefined;
140
140
  eventId?: undefined;
141
141
  } | {
142
+ error?: undefined;
142
143
  accepted: boolean;
143
144
  duplicate: boolean;
144
145
  eventId: string;
145
- error?: undefined;
146
146
  }>;