@agent-native/core 0.209.2 → 0.209.3-nightly-20261016212700

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 (79) hide show
  1. package/dist/a2a-claims.js +5 -5
  2. package/dist/agent/context-xray/plugin.js +2 -6
  3. package/dist/agent/context-xray/schema-store.d.ts +1 -0
  4. package/dist/agent/context-xray/schema-store.js +7 -0
  5. package/dist/agent/harness/store.js +3 -0
  6. package/dist/agent/observational-memory/plugin.js +2 -6
  7. package/dist/agent/observational-memory/store.js +3 -0
  8. package/dist/agent/run-store.js +3 -0
  9. package/dist/agent/tool-approval-store.js +4 -0
  10. package/dist/automation/index.d.ts +1 -1
  11. package/dist/chat-threads/store.js +3 -0
  12. package/dist/client/action-query-cache-context.d.ts +1 -0
  13. package/dist/client/action-query-cache-context.js +2 -0
  14. package/dist/client/action-query-cache.d.ts +1 -1
  15. package/dist/client/action-query-cache.js +34 -15
  16. package/dist/client/motion-text.css +8 -0
  17. package/dist/client/motion-text.d.ts +6 -0
  18. package/dist/client/motion-text.js +154 -29
  19. package/dist/client/use-action.js +18 -1
  20. package/dist/collab/routes.d.ts +1 -1
  21. package/dist/db/migrations.d.ts +1 -0
  22. package/dist/db/migrations.js +1 -1
  23. package/dist/db/runtime-facts.d.ts +7 -0
  24. package/dist/db/runtime-facts.js +18 -0
  25. package/dist/db/schema-catalog.js +4 -1
  26. package/dist/db/store-ledger.test-helpers.d.ts +5 -0
  27. package/dist/db/store-ledger.test-helpers.js +19 -0
  28. package/dist/db/store-registry.d.ts +34 -0
  29. package/dist/db/store-registry.generated.js +13 -0
  30. package/dist/db/store-registry.js +288 -46
  31. package/dist/db/versioned-store-migrations.d.ts +14 -0
  32. package/dist/db/versioned-store-migrations.js +37 -0
  33. package/dist/db/widen-columns.js +5 -2
  34. package/dist/extensions/html-shell.js +67 -0
  35. package/dist/integrations/remote-devices-store.js +3 -0
  36. package/dist/jobs/run-history.d.ts +3 -2
  37. package/dist/jobs/run-history.js +5 -5
  38. package/dist/jobs/scheduler-health.d.ts +3 -2
  39. package/dist/jobs/scheduler-health.js +5 -5
  40. package/dist/mcp/build-server.d.ts +3 -0
  41. package/dist/mcp/build-server.js +24 -7
  42. package/dist/mcp/server.js +40 -9
  43. package/dist/notifications/routes.d.ts +1 -1
  44. package/dist/oauth-tokens/store.js +3 -0
  45. package/dist/observability/routes.d.ts +4 -4
  46. package/dist/observability/store.js +4 -0
  47. package/dist/org/auto-join-domain.js +23 -15
  48. package/dist/org/context.d.ts +12 -0
  49. package/dist/org/context.js +27 -19
  50. package/dist/org/handlers.js +31 -13
  51. package/dist/org/index.d.ts +2 -1
  52. package/dist/org/index.js +1 -0
  53. package/dist/org/plugin.js +2 -4
  54. package/dist/org/schema-store.d.ts +1 -0
  55. package/dist/org/schema-store.js +13 -0
  56. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  57. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  58. package/dist/resources/store.js +5 -2
  59. package/dist/server/agent-access.js +2 -6
  60. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  61. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  62. package/dist/server/auth-plugin.js +2 -6
  63. package/dist/server/auth.js +22 -2
  64. package/dist/server/better-auth-migrations.d.ts +4 -2
  65. package/dist/server/better-auth-migrations.js +18 -6
  66. package/dist/server/identity-sso-store.js +3 -0
  67. package/dist/server/realtime-token.d.ts +1 -1
  68. package/dist/server/release-migrations.d.ts +1 -1
  69. package/dist/server/release-migrations.js +3 -65
  70. package/dist/server/release-schema.js +10 -2
  71. package/dist/server/short-lived-token.d.ts +3 -2
  72. package/dist/server/short-lived-token.js +3 -2
  73. package/dist/triggers/actions/manage-automation.d.ts +3 -3
  74. package/dist/triggers/event-queue.d.ts +4 -2
  75. package/dist/triggers/event-queue.js +38 -38
  76. package/dist/usage/alerts-store.js +3 -0
  77. package/dist/workspace-connections/groups.js +4 -0
  78. package/dist/workspace-connections/store.js +3 -0
  79. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { type MigrationEntry } from "../db/migrations.js";
1
+ import type { MigrationEntry } from "../db/migrations.js";
2
2
  export declare const AUTOMATION_SCHEDULER_LEASE_MS: number;
3
3
  export declare const AUTOMATION_SCHEDULER_LEASE_RENEWAL_MS = 60000;
4
4
  export declare class AutomationSchedulerLeaseLostError extends Error {
@@ -6,7 +6,8 @@ export declare class AutomationSchedulerLeaseLostError extends Error {
6
6
  constructor(cause?: unknown);
7
7
  }
8
8
  export declare const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS: MigrationEntry[];
9
- export declare function runAutomationSchedulerHealthMigrations(nitroApp: unknown): Promise<void>;
9
+ /** Kept for callers of the public export; the store owns these migrations. */
10
+ export declare function runAutomationSchedulerHealthMigrations(_nitroApp?: unknown): Promise<void>;
10
11
  export interface AutomationSchedulerHealth {
11
12
  appId: string;
12
13
  orgId: string | null;
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { getDbExec } from "../db/client.js";
3
3
  import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
4
- import { runMigrations } from "../db/migrations.js";
5
4
  import { defineStore } from "../db/store-registry.js";
5
+ import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
6
6
  import { automationRecoveryMessagesForLocale } from "../localization/automation-recovery-messages.js";
7
7
  const TABLE = "automation_scheduler_health";
8
8
  const DEFAULT_APP_ID = "default";
@@ -50,10 +50,9 @@ export const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS = [
50
50
  `,
51
51
  },
52
52
  ];
53
- export async function runAutomationSchedulerHealthMigrations(nitroApp) {
54
- await runMigrations(AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS, {
55
- table: "_automation_scheduler_health_migrations",
56
- })(nitroApp);
53
+ /** Kept for callers of the public export; the store owns these migrations. */
54
+ export async function runAutomationSchedulerHealthMigrations(_nitroApp) {
55
+ await schedulerHealthStore.ready();
57
56
  }
58
57
  function normalizeAppId(appId) {
59
58
  const normalized = appId?.trim();
@@ -96,6 +95,7 @@ export const schedulerHealthStore = defineStore({
96
95
  await ensureIndexExists(`idx_${TABLE}_updated`, indexSql);
97
96
  },
98
97
  },
98
+ ...fromMigrationEntries("_automation_scheduler_health_migrations", AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS),
99
99
  ],
100
100
  });
101
101
  export function ensureHealthTable() {
@@ -156,6 +156,9 @@ export declare class McpIdentityVerificationUnavailableError extends Error {
156
156
  readonly cause: unknown;
157
157
  constructor(cause: unknown);
158
158
  }
159
+ export declare class McpIdentityVerificationRefusedError extends Error {
160
+ constructor();
161
+ }
159
162
  export type VerifyAuthResult = {
160
163
  authed: boolean;
161
164
  identity?: MCPCallerIdentity;
@@ -2843,6 +2843,12 @@ export class McpIdentityVerificationUnavailableError extends Error {
2843
2843
  this.cause = cause;
2844
2844
  }
2845
2845
  }
2846
+ export class McpIdentityVerificationRefusedError extends Error {
2847
+ constructor() {
2848
+ super("MCP identity verification was refused");
2849
+ this.name = "McpIdentityVerificationRefusedError";
2850
+ }
2851
+ }
2846
2852
  async function verifyA2AJwtForMcp(token, resourceUrl) {
2847
2853
  const jose = await import("jose");
2848
2854
  let unverifiedPayload = null;
@@ -3245,6 +3251,9 @@ export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
3245
3251
  if (error instanceof McpIdentityVerificationUnavailableError) {
3246
3252
  return { authed: false, unavailable: true };
3247
3253
  }
3254
+ if (error instanceof McpIdentityVerificationRefusedError) {
3255
+ return { authed: false, refusal: "invalid" };
3256
+ }
3248
3257
  throw error;
3249
3258
  }
3250
3259
  if (payload) {
@@ -3321,21 +3330,29 @@ async function matchStaticAccessToken(token, accessTokens, ownerEmailHeader) {
3321
3330
  export async function resolveOrgIdFromDomain(orgDomain) {
3322
3331
  if (!orgDomain)
3323
3332
  return undefined;
3333
+ const { lookupOrgByDomain } = await import("../org/context.js");
3334
+ let result;
3324
3335
  try {
3325
- const { resolveOrgByDomain } = await import("../org/context.js");
3326
- const org = await resolveOrgByDomain(orgDomain);
3327
- return org?.orgId ?? undefined;
3336
+ result = await lookupOrgByDomain(orgDomain);
3328
3337
  }
3329
- catch {
3330
- return undefined;
3338
+ catch (cause) {
3339
+ throw new McpIdentityVerificationUnavailableError(cause);
3340
+ }
3341
+ if (result.status === "ambiguous") {
3342
+ throw new McpIdentityVerificationRefusedError();
3331
3343
  }
3344
+ return result.status === "found" ? result.org.orgId : undefined;
3332
3345
  }
3333
3346
  export async function resolveMcpIdentityOrgId(identity) {
3334
3347
  if (identity?.orgId !== undefined)
3335
3348
  return identity.orgId ?? undefined;
3336
- const orgIdFromDomain = await resolveOrgIdFromDomain(identity?.orgDomain);
3337
- if (orgIdFromDomain)
3349
+ if (identity?.orgDomain !== undefined) {
3350
+ const orgIdFromDomain = await resolveOrgIdFromDomain(identity.orgDomain);
3351
+ if (!orgIdFromDomain) {
3352
+ throw new McpIdentityVerificationRefusedError();
3353
+ }
3338
3354
  return orgIdFromDomain;
3355
+ }
3339
3356
  const userEmail = identity?.userEmail?.trim();
3340
3357
  if (!userEmail)
3341
3358
  return undefined;
@@ -8,7 +8,7 @@ import { getH3App } from "../server/framework-request-handler.js";
8
8
  import { getOrigin } from "../server/google-oauth.js";
9
9
  import { readBody } from "../server/h3-helpers.js";
10
10
  import { trackMcpInitialize } from "./analytics.js";
11
- import { createMCPServerForRequest, verifyAuth, McpIdentityVerificationUnavailableError, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, McpDirectoryProfileValidationError, validateMcpDirectoryProfile, validateMcpDirectoryWidgetDomain, selectMcpActionSurface, selectMcpDirectoryWidgetReadActions, selectMcpDirectoryWidgetWriteActions, } from "./build-server.js";
11
+ import { createMCPServerForRequest, verifyAuth, McpIdentityVerificationUnavailableError, McpIdentityVerificationRefusedError, getAccessTokens, resolveOrgIdFromDomain, resolveMcpIdentityOrgId, buildLinkArtifacts, McpDirectoryProfileValidationError, validateMcpDirectoryProfile, validateMcpDirectoryWidgetDomain, selectMcpActionSurface, selectMcpDirectoryWidgetReadActions, selectMcpDirectoryWidgetWriteActions, } from "./build-server.js";
12
12
  import { buildMcpOAuthChallenge, getMcpOAuthAudiences, getMcpOAuthIssuer, getMcpOAuthProtectedResourceMetadataUrl, getMcpOAuthResource, } from "./oauth-route.js";
13
13
  import { MCP_DIRECTORY_ROUTE_PREFIX, MCP_PUBLIC_ROUTE_PREFIX, MCP_ROUTE_PREFIXES, joinMcpRoute, } from "./route-paths.js";
14
14
  export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, selectMcpDirectoryWidgetReadActions, selectMcpDirectoryWidgetWriteActions, };
@@ -183,6 +183,19 @@ function directoryProfileUnavailable(event, validationError) {
183
183
  message: "The MCP directory is unavailable because its profile or widget origin is invalid.",
184
184
  };
185
185
  }
186
+ function identityVerificationUnavailable(event) {
187
+ setResponseStatus(event, 503);
188
+ setResponseHeader(event, "Retry-After", "5");
189
+ return {
190
+ error: "Service Unavailable",
191
+ message: CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE,
192
+ };
193
+ }
194
+ function identityVerificationRefused(event, routePath) {
195
+ setResponseStatus(event, 401);
196
+ setResponseHeader(event, "WWW-Authenticate", buildMcpOAuthChallenge(event, routePath, "invalid"));
197
+ return buildUnauthorizedBody(event, routePath, "invalid");
198
+ }
186
199
  // ---------------------------------------------------------------------------
187
200
  // handleMcpRequest — runtime-agnostic MCP request handler
188
201
  // ---------------------------------------------------------------------------
@@ -245,14 +258,13 @@ async function handleMcpRequestInternal(event, config, routePath = MCP_PUBLIC_RO
245
258
  });
246
259
  }
247
260
  catch (error) {
248
- if (!(error instanceof McpIdentityVerificationUnavailableError))
249
- throw error;
250
- setResponseStatus(event, 503);
251
- setResponseHeader(event, "Retry-After", "5");
252
- return {
253
- error: "Service Unavailable",
254
- message: CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE,
255
- };
261
+ if (error instanceof McpIdentityVerificationUnavailableError) {
262
+ return identityVerificationUnavailable(event);
263
+ }
264
+ if (error instanceof McpIdentityVerificationRefusedError) {
265
+ return identityVerificationRefused(event, routePath);
266
+ }
267
+ throw error;
256
268
  }
257
269
  if (!authResult.authed && authResult.unavailable) {
258
270
  // The token is valid but its org membership could not be checked. No auth
@@ -270,6 +282,25 @@ async function handleMcpRequestInternal(event, config, routePath = MCP_PUBLIC_RO
270
282
  setResponseHeader(event, "WWW-Authenticate", buildMcpOAuthChallenge(event, routePath, authResult.refusal));
271
283
  return buildUnauthorizedBody(event, routePath, authResult.refusal);
272
284
  }
285
+ if (authResult.identity?.orgId === undefined &&
286
+ authResult.identity?.orgDomain !== undefined) {
287
+ try {
288
+ const orgId = await resolveMcpIdentityOrgId(authResult.identity);
289
+ authResult = {
290
+ ...authResult,
291
+ identity: { ...authResult.identity, orgId },
292
+ };
293
+ }
294
+ catch (error) {
295
+ if (error instanceof McpIdentityVerificationUnavailableError) {
296
+ return identityVerificationUnavailable(event);
297
+ }
298
+ if (error instanceof McpIdentityVerificationRefusedError) {
299
+ return identityVerificationRefused(event, routePath);
300
+ }
301
+ throw error;
302
+ }
303
+ }
273
304
  const body = method === "POST" ? await readBody(event) : undefined;
274
305
  onMcpMethod?.(directoryLogMethod(body) ?? method);
275
306
  const initializeRequest = body
@@ -16,6 +16,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  } | {
17
17
  count?: undefined;
18
18
  updated?: undefined;
19
- error?: undefined;
20
19
  ok: boolean;
20
+ error?: undefined;
21
21
  }>>;
@@ -1,8 +1,10 @@
1
1
  import { getDbExec } from "../db/client.js";
2
2
  import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
3
3
  import { defineStore } from "../db/store-registry.js";
4
+ import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
4
5
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
5
6
  import { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "../secrets/crypto.js";
7
+ import { OAUTH_TOKEN_MIGRATIONS, OAUTH_TOKEN_MIGRATIONS_TABLE, } from "./migrations.js";
6
8
  function serializeTokens(tokens) {
7
9
  return encryptSecretValue(JSON.stringify(tokens));
8
10
  }
@@ -79,6 +81,7 @@ export const oauthTokensStore = defineStore({
79
81
  await widenIntColumnsToBigInt("oauth_tokens", ["updated_at"], exec);
80
82
  },
81
83
  },
84
+ ...fromMigrationEntries(OAUTH_TOKEN_MIGRATIONS_TABLE, OAUTH_TOKEN_MIGRATIONS, { data: [4, 6] }),
82
85
  ],
83
86
  });
84
87
  export function ensureTable() {
@@ -33,26 +33,26 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
33
33
  spans?: undefined;
34
34
  ok?: undefined;
35
35
  } | {
36
+ error?: undefined;
36
37
  message?: undefined;
37
38
  summary: import("./types.js").TraceSummary;
38
39
  spans: import("./types.js").TraceSpan[];
39
- error?: undefined;
40
40
  ok?: undefined;
41
41
  } | {
42
+ error?: undefined;
42
43
  message?: undefined;
43
44
  summary?: undefined;
44
45
  spans?: undefined;
45
46
  traceMissing: true;
46
47
  id: string;
47
- error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
+ error?: undefined;
50
51
  message?: undefined;
51
52
  summary?: undefined;
52
53
  spans?: undefined;
53
54
  traceMissing?: undefined;
54
55
  id: string;
55
- error?: undefined;
56
56
  ok?: undefined;
57
57
  } | {
58
58
  message?: undefined;
@@ -61,9 +61,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
61
61
  error: any;
62
62
  ok?: undefined;
63
63
  } | {
64
+ error?: undefined;
64
65
  message?: undefined;
65
66
  summary?: undefined;
66
67
  spans?: undefined;
67
- error?: undefined;
68
68
  ok: boolean;
69
69
  }>>;
@@ -2,6 +2,8 @@ import { z } from "zod";
2
2
  import { getDbExec } from "../db/client.js";
3
3
  import { ensureTableExists, ensureColumnExists, ensureIndexExists, } from "../db/ddl-guard.js";
4
4
  import { defineStore } from "../db/store-registry.js";
5
+ import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
6
+ import { OBSERVABILITY_MIGRATIONS, OBSERVABILITY_MIGRATIONS_TABLE, } from "./migrations.js";
5
7
  import { sanitizeToolErrorMessage, TOOL_ERROR_CAPTURE_METADATA_KEY, TOOL_ERROR_DETAIL_METADATA_KEY, } from "./trace-error.js";
6
8
  import { redactSensitiveFields } from "./trace-redaction.js";
7
9
  import { observabilityReviewThreadKey } from "./types.js";
@@ -118,6 +120,7 @@ function withUserFilter(baseWhere, baseArgs, userId, orgId) {
118
120
  }
119
121
  export const observabilityStore = defineStore({
120
122
  id: "observability",
123
+ after: ["chat_threads"],
121
124
  migrations: [
122
125
  {
123
126
  name: "baseline",
@@ -334,6 +337,7 @@ export const observabilityStore = defineStore({
334
337
  await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
335
338
  },
336
339
  },
340
+ ...fromMigrationEntries(OBSERVABILITY_MIGRATIONS_TABLE, OBSERVABILITY_MIGRATIONS, { data: [1, 2, 3] }),
337
341
  ],
338
342
  });
339
343
  export function ensureObservabilityTables() {
@@ -59,24 +59,32 @@ export async function autoJoinDomainMatchingOrgs(rawEmail, options = {}) {
59
59
  try {
60
60
  const res = await db.execute({
61
61
  sql: `SELECT o.id AS "orgId", o.identity_authority AS "identityAuthority",
62
- o.identity_id AS "identityId"
62
+ o.identity_id AS "identityId",
63
+ EXISTS (
64
+ SELECT 1 FROM org_members m
65
+ WHERE m.org_id = o.id
66
+ AND LOWER(m.email) = ?
67
+ AND m.federation_removal_pending_at IS NULL
68
+ ) AS "isMember"
63
69
  FROM organizations o
64
70
  WHERE LOWER(o.allowed_domain) = ?
65
- AND NOT EXISTS (
66
- SELECT 1
67
- FROM org_members m
68
- WHERE m.org_id = o.id
69
- AND LOWER(m.email) = ?
70
- AND m.federation_removal_pending_at IS NULL
71
- )
72
- ORDER BY o.created_at ASC`,
73
- args: [domain, email],
71
+ LIMIT 2`,
72
+ args: [email, domain],
74
73
  });
75
- matches = res.rows.map((r) => ({
76
- orgId: String(r.orgId ?? r.org_id),
77
- federated: Boolean(String(r.identityAuthority ?? r.identity_authority ?? "").trim() &&
78
- String(r.identityId ?? r.identity_id ?? "").trim()),
79
- }));
74
+ if (res.rows.length > 1)
75
+ return { joined: [], activeOrgId: null };
76
+ if (res.rows.length === 1) {
77
+ const row = res.rows[0];
78
+ if (Boolean(row.isMember ?? row.is_member)) {
79
+ return { joined: [], activeOrgId: null };
80
+ }
81
+ matches = [
82
+ {
83
+ orgId: String(row.orgId ?? row.org_id),
84
+ federated: Boolean(String(row.identityAuthority ?? row.identity_authority ?? "").trim() && String(row.identityId ?? row.identity_id ?? "").trim()),
85
+ },
86
+ ];
87
+ }
80
88
  }
81
89
  catch {
82
90
  return { joined: [], activeOrgId: null };
@@ -56,6 +56,18 @@ export declare function bootstrapAdminOrganization(rawEmail: string, event?: H3E
56
56
  export declare function getOrgDomain(orgId: string): Promise<string | null>;
57
57
  export declare function getOrgA2ASecret(orgId: string): Promise<string | null>;
58
58
  export declare function getA2ASecretByDomain(domain: string): Promise<string | null>;
59
+ export type OrgDomainLookup = {
60
+ status: "found";
61
+ org: {
62
+ orgId: string;
63
+ orgName: string;
64
+ };
65
+ } | {
66
+ status: "not-found";
67
+ } | {
68
+ status: "ambiguous";
69
+ };
70
+ export declare function lookupOrgByDomain(domain: string): Promise<OrgDomainLookup>;
59
71
  export declare function resolveOrgByDomain(domain: string): Promise<{
60
72
  orgId: string;
61
73
  orgName: string;
@@ -755,31 +755,35 @@ export async function getOrgA2ASecret(orgId) {
755
755
  export async function getA2ASecretByDomain(domain) {
756
756
  const exec = getDbExec();
757
757
  const { rows } = await exec.execute({
758
- sql: `SELECT a2a_secret FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
758
+ sql: `SELECT a2a_secret FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
759
759
  args: [domain.toLowerCase()],
760
760
  });
761
- if (!rows[0])
761
+ if (rows.length !== 1)
762
762
  return null;
763
763
  const secret = String(rows[0].a2a_secret || "");
764
764
  return secret || null;
765
765
  }
766
- export async function resolveOrgByDomain(domain) {
767
- try {
768
- const exec = getDbExec();
769
- const { rows } = await exec.execute({
770
- sql: `SELECT id, name FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
771
- args: [domain.toLowerCase()],
772
- });
773
- if (!rows[0])
774
- return null;
775
- return {
766
+ export async function lookupOrgByDomain(domain) {
767
+ const exec = getDbExec();
768
+ const { rows } = await exec.execute({
769
+ sql: `SELECT id, name FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
770
+ args: [domain.toLowerCase()],
771
+ });
772
+ if (rows.length === 0)
773
+ return { status: "not-found" };
774
+ if (rows.length > 1)
775
+ return { status: "ambiguous" };
776
+ return {
777
+ status: "found",
778
+ org: {
776
779
  orgId: String(rows[0].id),
777
780
  orgName: String(rows[0].name),
778
- };
779
- }
780
- catch {
781
- return null;
782
- }
781
+ },
782
+ };
783
+ }
784
+ export async function resolveOrgByDomain(domain) {
785
+ const result = await lookupOrgByDomain(domain);
786
+ return result.status === "found" ? result.org : null;
783
787
  }
784
788
  export async function resolveA2AOrganizationMetadataByDomain(domain) {
785
789
  const normalizedDomain = domain.trim().toLowerCase();
@@ -787,9 +791,11 @@ export async function resolveA2AOrganizationMetadataByDomain(domain) {
787
791
  return null;
788
792
  const { rows } = await getDbExec().execute({
789
793
  sql: `SELECT id, allowed_domain
790
- FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
794
+ FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
791
795
  args: [normalizedDomain],
792
796
  });
797
+ if (rows.length !== 1)
798
+ return null;
793
799
  const row = rows[0];
794
800
  if (!row)
795
801
  return null;
@@ -826,9 +832,11 @@ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
826
832
  return null;
827
833
  const { rows } = await getDbExec().execute({
828
834
  sql: `SELECT id, allowed_domain, a2a_secret
829
- FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
835
+ FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
830
836
  args: [normalizedDomain],
831
837
  });
838
+ if (rows.length !== 1)
839
+ return null;
832
840
  const row = rows[0];
833
841
  if (!row)
834
842
  return null;
@@ -1439,21 +1439,33 @@ export const setDomainHandler = defineEventHandler(async (event) => {
1439
1439
  }
1440
1440
  }
1441
1441
  const e = await exec();
1442
- if (raw) {
1443
- const existing = await e.execute({
1444
- sql: `SELECT id FROM organizations WHERE LOWER(allowed_domain) = ? AND id != ? LIMIT 1`,
1445
- args: [raw, ctx.orgId],
1442
+ if (!e.transaction) {
1443
+ throw createError({
1444
+ statusCode: 503,
1445
+ message: "Organization domain updates require transaction support",
1446
1446
  });
1447
- if (existing.rows.length > 0) {
1448
- throw createError({
1449
- statusCode: 409,
1450
- message: "Another organization already uses this domain",
1447
+ }
1448
+ await e.transaction(async (tx) => {
1449
+ if (raw) {
1450
+ await tx.execute({
1451
+ sql: `SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))`,
1452
+ args: [`agent-native:org-domain:${raw}`],
1453
+ });
1454
+ const existing = await tx.execute({
1455
+ sql: `SELECT id FROM organizations WHERE LOWER(allowed_domain) = ? AND id != ? LIMIT 1`,
1456
+ args: [raw, ctx.orgId],
1451
1457
  });
1458
+ if (existing.rows.length > 0) {
1459
+ throw createError({
1460
+ statusCode: 409,
1461
+ message: "Another organization already uses this domain",
1462
+ });
1463
+ }
1452
1464
  }
1453
- }
1454
- await e.execute({
1455
- sql: `UPDATE organizations SET allowed_domain = ? WHERE id = ?`,
1456
- args: [raw, ctx.orgId],
1465
+ await tx.execute({
1466
+ sql: `UPDATE organizations SET allowed_domain = ? WHERE id = ?`,
1467
+ args: [raw, ctx.orgId],
1468
+ });
1457
1469
  });
1458
1470
  invalidateDomainMatchCache();
1459
1471
  invalidateMemberOrgCaches();
@@ -1758,7 +1770,7 @@ export const receiveA2ASecretHandler = defineEventHandler(async (event) => {
1758
1770
  }
1759
1771
  const e = await exec();
1760
1772
  const orgRes = await e.execute({
1761
- sql: `SELECT id, a2a_secret FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
1773
+ sql: `SELECT id, a2a_secret FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
1762
1774
  args: [orgDomain],
1763
1775
  });
1764
1776
  if (orgRes.rows.length === 0) {
@@ -1767,6 +1779,12 @@ export const receiveA2ASecretHandler = defineEventHandler(async (event) => {
1767
1779
  message: "No local org matches that domain",
1768
1780
  });
1769
1781
  }
1782
+ if (orgRes.rows.length > 1) {
1783
+ throw createError({
1784
+ statusCode: 409,
1785
+ message: "Multiple local organizations match that domain",
1786
+ });
1787
+ }
1770
1788
  const row = orgRes.rows[0];
1771
1789
  const localOrgId = String(row.id);
1772
1790
  const existingSecret = String(row.a2a_secret ?? "") || null;
@@ -7,6 +7,7 @@ export declare const getOrgA2ASecret: typeof import("./context.js").getOrgA2ASec
7
7
  export declare const getA2ASecretByDomain: typeof import("./context.js").getA2ASecretByDomain;
8
8
  export declare const isSoleOrgDomain: typeof import("./context.js").isSoleOrgDomain;
9
9
  export declare const resolveOrgByDomain: typeof import("./context.js").resolveOrgByDomain;
10
+ export declare const lookupOrgByDomain: typeof import("./context.js").lookupOrgByDomain;
10
11
  export declare const resolveOrgIdForEmail: typeof import("./context.js").resolveOrgIdForEmail;
11
12
  export declare const resolveActiveOrgIdForStorageCredential: typeof import("./context.js").resolveActiveOrgIdForStorageCredential;
12
13
  export declare const createOrganization: typeof import("./context.js").createOrganization;
@@ -161,9 +162,9 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
161
162
  soloDeploymentAdmin: boolean;
162
163
  }>>;
163
164
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
165
+ name?: undefined;
164
166
  success: boolean;
165
167
  id?: undefined;
166
- name?: undefined;
167
168
  role?: undefined;
168
169
  } | {
169
170
  success?: undefined;
package/dist/org/index.js CHANGED
@@ -21,6 +21,7 @@ export const getOrgA2ASecret = lazyFunction(loadOrgContext, "getOrgA2ASecret");
21
21
  export const getA2ASecretByDomain = lazyFunction(loadOrgContext, "getA2ASecretByDomain");
22
22
  export const isSoleOrgDomain = lazyFunction(loadOrgContext, "isSoleOrgDomain");
23
23
  export const resolveOrgByDomain = lazyFunction(loadOrgContext, "resolveOrgByDomain");
24
+ export const lookupOrgByDomain = lazyFunction(loadOrgContext, "lookupOrgByDomain");
24
25
  export const resolveOrgIdForEmail = lazyFunction(loadOrgContext, "resolveOrgIdForEmail");
25
26
  export const resolveActiveOrgIdForStorageCredential = lazyFunction(loadOrgContext, "resolveActiveOrgIdForStorageCredential");
26
27
  export const createOrganization = lazyFunction(loadOrgContext, "createOrganization");
@@ -1,13 +1,12 @@
1
1
  import { defineEventHandler, setResponseStatus, getMethod, getRequestURL, } from "h3";
2
- import { runMigrations } from "../db/migrations.js";
3
2
  import { registerFeatureFlags } from "../feature-flags/registry.js";
4
3
  import { awaitBootstrap, getH3App, FRAMEWORK_PREFIX, markDefaultPluginProvided, } from "../server/framework-request-handler.js";
5
4
  import { listAppRolesHandler, setAppRoleHandler, getAppPermissionsHandler, setAppPermissionsHandler, resetAppPermissionHandler, } from "./app-roles-handlers.js";
6
5
  import { listSSOProvidersHandler, createSSOProviderHandler, verifySSOProviderHandler, deleteSSOProviderHandler, getSCIMHandler, createSCIMHandler, deleteSCIMHandler, } from "./enterprise-auth-handlers.js";
7
6
  import { CROSS_APP_ORG_FEDERATION_FLAG } from "./feature-flags.js";
8
7
  import { getMyOrgHandler, createOrgHandler, updateOrgHandler, deleteOrgHandler, switchOrgHandler, listMembersHandler, removeMemberHandler, retryPendingFederatedRemovalHandler, changeMemberRoleHandler, listInvitationsHandler, createInvitationHandler, acceptInvitationHandler, joinByDomainHandler, setDomainHandler, setWorkspaceUrlHandler, setRequiredAuthProviderHandler, revealA2ASecretHandler, setA2ASecretHandler, syncA2ASecretHandler, receiveA2ASecretHandler, setWorkspaceAppDefaultVisibilityHandler, setOrgVisualIdentityHandler, } from "./handlers.js";
9
- import { ORG_MIGRATIONS } from "./migrations.js";
10
8
  import { readWorkspacePrivateIconHandler, uploadWorkspacePrivateIconHandler, } from "./private-icon-handlers.js";
9
+ import { orgStore } from "./schema-store.js";
11
10
  const ORG_PREFIX = `${FRAMEWORK_PREFIX}/org`;
12
11
  async function workspaceIconUploadResponse(event) {
13
12
  try {
@@ -64,12 +63,11 @@ async function workspaceIconUploadResponse(event) {
64
63
  * POST /_agent-native/org/a2a-secret/receive — accept a peer's secret push (JWT-auth, no session)
65
64
  */
66
65
  export function createOrgPlugin() {
67
- const migrate = runMigrations(ORG_MIGRATIONS, { table: "_org_migrations" });
68
66
  return async (nitroApp) => {
69
67
  registerFeatureFlags([CROSS_APP_ORG_FEDERATION_FLAG]);
70
68
  markDefaultPluginProvided(nitroApp, "org");
71
69
  await awaitBootstrap(nitroApp);
72
- await migrate(nitroApp);
70
+ await orgStore.ready();
73
71
  const app = getH3App(nitroApp);
74
72
  app.use(`${ORG_PREFIX}/private-icons`, defineEventHandler(async (event) => {
75
73
  const tail = getRequestURL(event).pathname || "/";
@@ -0,0 +1 @@
1
+ export declare const orgStore: import("../db/store-registry.js").Store;
@@ -0,0 +1,13 @@
1
+ import { defineStore } from "../db/store-registry.js";
2
+ import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
3
+ import { ORG_MIGRATIONS } from "./migrations.js";
4
+ export const orgStore = defineStore({
5
+ id: "org",
6
+ // Entries 1024 and 1032 touch the review-suggestion and audit tables; on main
7
+ // every store ran before this list, so keep that order at release.
8
+ after: ["audit", "review_suggestions"],
9
+ migrations: fromMigrationEntries("_org_migrations", ORG_MIGRATIONS, {
10
+ ranAtBoot: true,
11
+ data: [1009, 1018],
12
+ }),
13
+ });
@@ -75,9 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- id?: undefined;
79
- deleted?: undefined;
80
78
  message?: undefined;
79
+ id?: undefined;
81
80
  providers: {
82
81
  id: string;
83
82
  label: string;
@@ -90,31 +89,32 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
90
89
  count: number;
91
90
  provider?: undefined;
92
91
  found?: undefined;
92
+ deleted?: undefined;
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
+ message?: undefined;
96
97
  id?: undefined;
97
- deleted?: undefined;
98
98
  count?: undefined;
99
- message?: undefined;
100
99
  providers?: undefined;
101
100
  found: boolean;
102
101
  provider: import("../custom-registry.js").CustomProviderConfig;
102
+ deleted?: undefined;
103
103
  registered?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
- deleted?: undefined;
107
- count?: undefined;
108
106
  message?: undefined;
107
+ count?: undefined;
109
108
  providers?: undefined;
110
109
  provider?: undefined;
111
110
  found: boolean;
112
111
  id: string;
112
+ deleted?: undefined;
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
- count?: undefined;
117
116
  message?: undefined;
117
+ count?: undefined;
118
118
  providers?: undefined;
119
119
  provider?: undefined;
120
120
  found?: undefined;
@@ -123,11 +123,11 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
123
123
  registered?: undefined;
124
124
  label?: undefined;
125
125
  } | {
126
- deleted?: undefined;
127
126
  count?: undefined;
128
127
  providers?: undefined;
129
128
  provider?: undefined;
130
129
  found?: undefined;
130
+ deleted?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
133
133
  label: string;