@agent-native/dispatch 0.40.23 → 0.40.24-nightly-20261015093558

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @agent-native/dispatch
2
2
 
3
+ ## 0.40.24-nightly-20261015093558
4
+
5
+ ### Patch Changes
6
+
7
+ - 2743662: Use the shared app permission policy for organization owner and admin access to Dispatch administration. Dispatch now requires `@agent-native/core` 0.206.0 or later, the first release with that policy.
8
+ - Updated dependencies [eb4cd30]
9
+ - Updated dependencies [ebf1cc7]
10
+ - Updated dependencies [e90b90a]
11
+ - Updated dependencies [5b201d1]
12
+ - Updated dependencies [db3a30e]
13
+ - Updated dependencies [b95626f]
14
+ - Updated dependencies [64bf791]
15
+ - Updated dependencies [e5a64ae]
16
+ - Updated dependencies [ef6a530]
17
+ - Updated dependencies [e6e1f56]
18
+ - Updated dependencies [4e49b3a]
19
+ - @agent-native/core@0.209.1-nightly-20261015093558
20
+ - @agent-native/toolkit@0.209.1-nightly-20261015093558
21
+
3
22
  ## 0.40.23
4
23
 
5
24
  ### Patch Changes
@@ -47,10 +47,10 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
47
47
  notes?: string | undefined;
48
48
  scope?: "org" | "user" | undefined;
49
49
  }, {
50
+ message?: undefined;
50
51
  id?: undefined;
51
52
  provider?: undefined;
52
53
  deleted?: undefined;
53
- message?: undefined;
54
54
  providers: {
55
55
  id: string;
56
56
  label: string;
@@ -65,30 +65,30 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
65
65
  registered?: undefined;
66
66
  label?: undefined;
67
67
  } | {
68
+ message?: undefined;
68
69
  id?: undefined;
69
- count?: undefined;
70
70
  deleted?: undefined;
71
- message?: undefined;
72
71
  providers?: undefined;
72
+ count?: undefined;
73
73
  found: boolean;
74
74
  provider: import("node_modules/@agent-native/core/dist/provider-api").CustomProviderConfig;
75
75
  registered?: undefined;
76
76
  label?: undefined;
77
77
  } | {
78
+ message?: undefined;
78
79
  provider?: undefined;
79
- count?: undefined;
80
80
  deleted?: undefined;
81
- message?: undefined;
82
81
  providers?: undefined;
82
+ count?: undefined;
83
83
  found: boolean;
84
84
  id: string;
85
85
  registered?: undefined;
86
86
  label?: undefined;
87
87
  } | {
88
- provider?: undefined;
89
- count?: undefined;
90
88
  message?: undefined;
89
+ provider?: undefined;
91
90
  providers?: undefined;
91
+ count?: undefined;
92
92
  found?: undefined;
93
93
  deleted: boolean;
94
94
  id: string;
@@ -96,9 +96,9 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
96
96
  label?: undefined;
97
97
  } | {
98
98
  provider?: undefined;
99
- count?: undefined;
100
99
  deleted?: undefined;
101
100
  providers?: undefined;
101
+ count?: undefined;
102
102
  found?: undefined;
103
103
  registered: boolean;
104
104
  id: string;
@@ -2,7 +2,7 @@ import { defineAppRoles } from "@agent-native/core/org";
2
2
  import { registerPackageActions, } from "@agent-native/core/server";
3
3
  import { dispatchActions } from "../actions/index.js";
4
4
  import { dispatchAccessDescriptor } from "../shared/app-roles.js";
5
- defineAppRoles(dispatchAccessDescriptor);
5
+ defineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });
6
6
  registerPackageActions(dispatchActions);
7
7
  let activeConfig = {};
8
8
  export function getDispatchConfig() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,sBAAsB,GAEvB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,cAAc,CAAC,wBAAwB,CAAC,CAAC;AAEzC,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAExC,IAAI,YAAY,GAAmB,EAAE,CAAC;AAEtC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAM,GAAmB,EAAE;IACvD,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,KAAK,QAAQ,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EACL,OAAO,IAAI,gBAAgB,EAC3B,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["import { defineAppRoles } from \"@agent-native/core/org\";\nimport {\n registerPackageActions,\n type NitroPluginDef,\n} from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../actions/index.js\";\nimport type { DispatchConfig } from \"../config.js\";\nimport { dispatchAccessDescriptor } from \"../shared/app-roles.js\";\n\ndefineAppRoles(dispatchAccessDescriptor);\n\nregisterPackageActions(dispatchActions);\n\nlet activeConfig: DispatchConfig = {};\n\nexport function getDispatchConfig(): DispatchConfig {\n return activeConfig;\n}\n\nexport function setupDispatch(config: DispatchConfig = {}): NitroPluginDef {\n activeConfig = Object.freeze({ ...config });\n return (nitroApp) => {\n void nitroApp;\n };\n}\n\nexport { default as dispatchAuthPlugin } from \"./plugins/auth.js\";\nexport { default as dispatchIntegrationsPlugin } from \"./plugins/integrations.js\";\nexport { default as dispatchAgentChatPlugin } from \"./plugins/agent-chat.js\";\nexport {\n default as dispatchDbPlugin,\n runDispatchMigrations,\n} from \"./plugins/db.js\";\nexport { default as dispatchCoreRoutesPlugin } from \"./plugins/core-routes.js\";\n\nexport type { DispatchConfig } from \"../config.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,sBAAsB,GAEvB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,cAAc,CAAC,wBAAwB,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AAEnE,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAExC,IAAI,YAAY,GAAmB,EAAE,CAAC;AAEtC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAM,GAAmB,EAAE;IACvD,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,KAAK,QAAQ,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EACL,OAAO,IAAI,gBAAgB,EAC3B,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["import { defineAppRoles } from \"@agent-native/core/org\";\nimport {\n registerPackageActions,\n type NitroPluginDef,\n} from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../actions/index.js\";\nimport type { DispatchConfig } from \"../config.js\";\nimport { dispatchAccessDescriptor } from \"../shared/app-roles.js\";\n\ndefineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });\n\nregisterPackageActions(dispatchActions);\n\nlet activeConfig: DispatchConfig = {};\n\nexport function getDispatchConfig(): DispatchConfig {\n return activeConfig;\n}\n\nexport function setupDispatch(config: DispatchConfig = {}): NitroPluginDef {\n activeConfig = Object.freeze({ ...config });\n return (nitroApp) => {\n void nitroApp;\n };\n}\n\nexport { default as dispatchAuthPlugin } from \"./plugins/auth.js\";\nexport { default as dispatchIntegrationsPlugin } from \"./plugins/integrations.js\";\nexport { default as dispatchAgentChatPlugin } from \"./plugins/agent-chat.js\";\nexport {\n default as dispatchDbPlugin,\n runDispatchMigrations,\n} from \"./plugins/db.js\";\nexport { default as dispatchCoreRoutesPlugin } from \"./plugins/core-routes.js\";\n\nexport type { DispatchConfig } from \"../config.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"app-roles.d.ts","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAsBlE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,OAAO,EACd,GAAG,CAAC,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
1
+ {"version":3,"file":"app-roles.d.ts","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAwBlE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,OAAO,EACd,GAAG,CAAC,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAoCf"}
@@ -4,7 +4,9 @@ import { ForbiddenError } from "@agent-native/core/sharing";
4
4
  import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
5
5
  let dispatchAccess;
6
6
  function getDispatchAccess() {
7
- return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor));
7
+ return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {
8
+ allowOrgAdmins: true,
9
+ }));
8
10
  }
9
11
  export async function authorizeDispatchAdmin(_args, ctx) {
10
12
  const email = ctx?.userEmail !== undefined ? ctx.userEmail : getRequestUserEmail();
@@ -31,8 +33,6 @@ export async function authorizeDispatchAdmin(_args, ctx) {
31
33
  if (!membership.active) {
32
34
  throw new ForbiddenError("Dispatch administration requires active organization membership.");
33
35
  }
34
- if (membership.role === "owner" || membership.role === "admin")
35
- return;
36
36
  await getDispatchAccess().assertPermission(["administer"], {
37
37
  userEmail: email,
38
38
  orgId,
@@ -1 +1 @@
1
- {"version":3,"file":"app-roles.js","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,+BAA+B,EAC/B,2BAA2B,EAE3B,wDAAwD,GACzD,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,IAAI,cAA2D,CAAC;AAEhE,SAAS,iBAAiB;IACxB,OAAO,CAAC,cAAc,KAAK,cAAc,CAAC,wBAAwB,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAc,EACd,GAAsB;IAEtB,MAAM,KAAK,GACT,GAAG,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,cAAc,CACtB,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO;IAC3B,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,wDAAwD,CACzE;YACE,KAAK;YACL,KAAK;SACN,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,+BAA+B,CAAC,KAAK,CAAC;YACtC,2BAA2B,EAAE,EAC7B,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,cAAc,CACtB,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO;IACvE,MAAM,iBAAiB,EAAE,CAAC,gBAAgB,CAAC,CAAC,YAAY,CAAC,EAAE;QACzD,SAAS,EAAE,KAAK;QAChB,KAAK;KACN,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ActionRunContext } from \"@agent-native/core/action\";\nimport {\n defineAppRoles,\n isMissingOrganizationTableError,\n isStandaloneDispatchRuntime,\n type AppRoles,\n validateFederatedOrganizationMembershipForCurrentRequest,\n} from \"@agent-native/core/org\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"@agent-native/core/server\";\nimport { ForbiddenError } from \"@agent-native/core/sharing\";\n\nimport { dispatchAccessDescriptor } from \"../../shared/app-roles.js\";\n\nlet dispatchAccess: AppRoles<\"admin\", \"administer\"> | undefined;\n\nfunction getDispatchAccess(): AppRoles<\"admin\", \"administer\"> {\n return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor));\n}\n\nexport async function authorizeDispatchAdmin(\n _args: unknown,\n ctx?: ActionRunContext,\n): Promise<void> {\n const email =\n ctx?.userEmail !== undefined ? ctx.userEmail : getRequestUserEmail();\n const orgId = ctx?.orgId !== undefined ? ctx.orgId : getRequestOrgId();\n if (!email?.trim()) {\n throw new ForbiddenError(\n \"Dispatch administration requires an authenticated user.\",\n );\n }\n if (!orgId?.trim()) return;\n let membership;\n try {\n membership = await validateFederatedOrganizationMembershipForCurrentRequest(\n {\n orgId,\n email,\n },\n );\n } catch (error) {\n if (\n isMissingOrganizationTableError(error) &&\n isStandaloneDispatchRuntime()\n ) {\n return;\n }\n throw error;\n }\n if (!membership.active) {\n throw new ForbiddenError(\n \"Dispatch administration requires active organization membership.\",\n );\n }\n if (membership.role === \"owner\" || membership.role === \"admin\") return;\n await getDispatchAccess().assertPermission([\"administer\"], {\n userEmail: email,\n orgId,\n });\n}\n"]}
1
+ {"version":3,"file":"app-roles.js","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,+BAA+B,EAC/B,2BAA2B,EAE3B,wDAAwD,GACzD,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,IAAI,cAA2D,CAAC;AAEhE,SAAS,iBAAiB;IACxB,OAAO,CAAC,cAAc,KAAK,cAAc,CAAC,wBAAwB,EAAE;QAClE,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAc,EACd,GAAsB;IAEtB,MAAM,KAAK,GACT,GAAG,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,cAAc,CACtB,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO;IAC3B,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,wDAAwD,CACzE;YACE,KAAK;YACL,KAAK;SACN,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,+BAA+B,CAAC,KAAK,CAAC;YACtC,2BAA2B,EAAE,EAC7B,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,cAAc,CACtB,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,MAAM,iBAAiB,EAAE,CAAC,gBAAgB,CAAC,CAAC,YAAY,CAAC,EAAE;QACzD,SAAS,EAAE,KAAK;QAChB,KAAK;KACN,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ActionRunContext } from \"@agent-native/core/action\";\nimport {\n defineAppRoles,\n isMissingOrganizationTableError,\n isStandaloneDispatchRuntime,\n type AppRoles,\n validateFederatedOrganizationMembershipForCurrentRequest,\n} from \"@agent-native/core/org\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"@agent-native/core/server\";\nimport { ForbiddenError } from \"@agent-native/core/sharing\";\n\nimport { dispatchAccessDescriptor } from \"../../shared/app-roles.js\";\n\nlet dispatchAccess: AppRoles<\"admin\", \"administer\"> | undefined;\n\nfunction getDispatchAccess(): AppRoles<\"admin\", \"administer\"> {\n return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {\n allowOrgAdmins: true,\n }));\n}\n\nexport async function authorizeDispatchAdmin(\n _args: unknown,\n ctx?: ActionRunContext,\n): Promise<void> {\n const email =\n ctx?.userEmail !== undefined ? ctx.userEmail : getRequestUserEmail();\n const orgId = ctx?.orgId !== undefined ? ctx.orgId : getRequestOrgId();\n if (!email?.trim()) {\n throw new ForbiddenError(\n \"Dispatch administration requires an authenticated user.\",\n );\n }\n if (!orgId?.trim()) return;\n let membership;\n try {\n membership = await validateFederatedOrganizationMembershipForCurrentRequest(\n {\n orgId,\n email,\n },\n );\n } catch (error) {\n if (\n isMissingOrganizationTableError(error) &&\n isStandaloneDispatchRuntime()\n ) {\n return;\n }\n throw error;\n }\n if (!membership.active) {\n throw new ForbiddenError(\n \"Dispatch administration requires active organization membership.\",\n );\n }\n await getDispatchAccess().assertPermission([\"administer\"], {\n userEmail: email,\n orgId,\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.40.23",
3
+ "version": "0.40.24-nightly-20261015093558",
4
4
  "description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -89,7 +89,7 @@
89
89
  "tailwind-merge": "^3.5.0",
90
90
  "vaul": "^1.1.2",
91
91
  "zod": "^4.3.6",
92
- "@agent-native/toolkit": "0.209.0"
92
+ "@agent-native/toolkit": "0.209.1-nightly-20261015093558"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@react-router/dev": "^8.1.0",
@@ -103,10 +103,10 @@
103
103
  "typescript-7": "npm:typescript@^7.0.2",
104
104
  "vite": "8.1.0",
105
105
  "vitest": "^4.1.5",
106
- "@agent-native/core": "0.209.0"
106
+ "@agent-native/core": "0.209.1-nightly-20261015093558"
107
107
  },
108
108
  "peerDependencies": {
109
- "@agent-native/core": ">=0.198.6",
109
+ "@agent-native/core": "0.209.1-nightly-20261015093558",
110
110
  "react": ">=19.2.7",
111
111
  "react-dom": ">=19.2.7",
112
112
  "react-router": ">=8"
@@ -8,7 +8,7 @@ import { dispatchActions } from "../actions/index.js";
8
8
  import type { DispatchConfig } from "../config.js";
9
9
  import { dispatchAccessDescriptor } from "../shared/app-roles.js";
10
10
 
11
- defineAppRoles(dispatchAccessDescriptor);
11
+ defineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });
12
12
 
13
13
  registerPackageActions(dispatchActions);
14
14
 
@@ -1,26 +1,33 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  const mocks = vi.hoisted(() => ({
4
- assertAny: vi.fn(),
5
- assertPermission: vi.fn(),
4
+ roles: [] as string[],
5
+ orgRole: "member",
6
+ member: true,
7
+ federated: false,
8
+ overrides: [] as { permission: string; roles_json: string }[],
9
+ execute: vi.fn(),
6
10
  getRequestOrgId: vi.fn(),
7
11
  getRequestUserEmail: vi.fn(),
8
12
  isStandaloneDispatchRuntime: vi.fn(),
9
13
  validateFederatedOrganizationMembershipForCurrentRequest: vi.fn(),
10
14
  }));
11
15
 
12
- vi.mock("@agent-native/core/org", () => ({
13
- defineAppRoles: () => ({
14
- assertAny: mocks.assertAny,
15
- assertPermission: mocks.assertPermission,
16
- }),
16
+ vi.mock("../../../../core/src/db/client.js", () => ({
17
+ getDbExec: () => ({ execute: mocks.execute }),
18
+ }));
19
+ vi.mock("../../../../core/src/org/federation.js", () => ({
20
+ validateFederatedOrganizationMembershipForCurrentRequest:
21
+ mocks.validateFederatedOrganizationMembershipForCurrentRequest,
22
+ }));
23
+ vi.mock("@agent-native/core/org", async () => ({
24
+ ...(await import("../../../../core/src/org/app-roles.js")),
17
25
  isMissingOrganizationTableError: (error: unknown) =>
18
26
  /(?:organizations|org_members).*does not exist/i.test(String(error)),
19
27
  isStandaloneDispatchRuntime: mocks.isStandaloneDispatchRuntime,
20
28
  validateFederatedOrganizationMembershipForCurrentRequest:
21
29
  mocks.validateFederatedOrganizationMembershipForCurrentRequest,
22
30
  }));
23
-
24
31
  vi.mock("@agent-native/core/server", () => ({
25
32
  getRequestOrgId: mocks.getRequestOrgId,
26
33
  getRequestUserEmail: mocks.getRequestUserEmail,
@@ -39,57 +46,87 @@ const context = {
39
46
  describe("authorizeDispatchAdmin", () => {
40
47
  beforeEach(() => {
41
48
  vi.clearAllMocks();
49
+ mocks.roles = [];
50
+ mocks.orgRole = "member";
51
+ mocks.member = true;
52
+ mocks.federated = false;
53
+ mocks.overrides = [];
54
+ mocks.execute.mockImplementation(async ({ sql }: { sql: string }) => ({
55
+ rows: sql.includes("app_permission_overrides")
56
+ ? mocks.overrides
57
+ : mocks.member
58
+ ? [
59
+ {
60
+ roles: mocks.roles,
61
+ orgRole: mocks.orgRole,
62
+ identityAuthority: mocks.federated
63
+ ? "https://hub.example.test"
64
+ : null,
65
+ identityId: mocks.federated ? "org_hub" : null,
66
+ },
67
+ ]
68
+ : [],
69
+ }));
42
70
  mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
43
71
  { active: true, role: "member" },
44
72
  );
45
- mocks.assertAny.mockResolvedValue("admin");
46
- mocks.assertPermission.mockResolvedValue(undefined);
47
73
  mocks.getRequestOrgId.mockReturnValue(undefined);
48
74
  mocks.getRequestUserEmail.mockReturnValue(undefined);
49
75
  mocks.isStandaloneDispatchRuntime.mockReturnValue(false);
50
76
  });
51
77
 
52
78
  it("denies an organization member without the Dispatch admin role", async () => {
53
- mocks.assertAny.mockRejectedValue(
54
- new ForbiddenError("Requires dispatch role admin"),
55
- );
56
- mocks.assertPermission.mockRejectedValue(
57
- new ForbiddenError("Requires dispatch role admin"),
79
+ await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
80
+ "Requires dispatch permission administer",
58
81
  );
82
+ });
59
83
 
84
+ it.each(["owner", "admin"])(
85
+ "allows an organization %s without app roles through the shared policy",
86
+ async (role) => {
87
+ mocks.orgRole = role;
88
+ mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
89
+ { active: true, role },
90
+ );
91
+ await expect(
92
+ authorizeDispatchAdmin({}, context),
93
+ ).resolves.toBeUndefined();
94
+ expect(mocks.execute).toHaveBeenCalledWith(
95
+ expect.objectContaining({
96
+ args: ["dispatch", "org-1", "member@example.test"],
97
+ }),
98
+ );
99
+ },
100
+ );
101
+
102
+ it("uses the identity hub role over a stale local organization admin role", async () => {
103
+ mocks.federated = true;
104
+ mocks.orgRole = "admin";
60
105
  await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
61
- "Requires dispatch role admin",
106
+ "Requires dispatch permission administer",
62
107
  );
63
- expect(
64
- mocks.validateFederatedOrganizationMembershipForCurrentRequest,
65
- ).toHaveBeenCalledWith({
66
- orgId: "org-1",
67
- email: "member@example.test",
68
- });
69
- expect(mocks.assertPermission).toHaveBeenCalledWith(["administer"], {
70
- orgId: "org-1",
71
- userEmail: "member@example.test",
72
- });
73
108
  });
74
109
 
75
- it("allows an organization admin without an app-role assignment", async () => {
110
+ it("allows an organization admin granted by the identity hub", async () => {
111
+ mocks.federated = true;
76
112
  mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
77
113
  { active: true, role: "admin" },
78
114
  );
79
-
80
115
  await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
81
- expect(mocks.assertAny).not.toHaveBeenCalled();
116
+ expect(
117
+ mocks.validateFederatedOrganizationMembershipForCurrentRequest,
118
+ ).toHaveBeenCalledTimes(2);
82
119
  });
83
120
 
84
121
  it("denies a stale linked organization admin", async () => {
122
+ mocks.orgRole = "admin";
85
123
  mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
86
124
  { active: false, role: null },
87
125
  );
88
-
89
126
  await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
90
127
  "active organization membership",
91
128
  );
92
- expect(mocks.assertAny).not.toHaveBeenCalled();
129
+ expect(mocks.execute).not.toHaveBeenCalled();
93
130
  });
94
131
 
95
132
  it("allows standalone administration when the org schema is absent", async () => {
@@ -97,27 +134,28 @@ describe("authorizeDispatchAdmin", () => {
97
134
  mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockRejectedValue(
98
135
  new Error('relation "org_members" does not exist'),
99
136
  );
100
-
101
137
  await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
102
- expect(mocks.assertAny).not.toHaveBeenCalled();
138
+ expect(mocks.execute).not.toHaveBeenCalled();
103
139
  });
104
140
 
105
141
  it("allows a member with the Dispatch admin role", async () => {
142
+ mocks.roles = ["admin"];
106
143
  await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
107
- expect(mocks.assertPermission).toHaveBeenCalledWith(["administer"], {
108
- orgId: "org-1",
109
- userEmail: "member@example.test",
110
- });
144
+ });
145
+
146
+ it("honors a permission override for an app admin", async () => {
147
+ mocks.roles = ["admin"];
148
+ mocks.overrides = [{ permission: "administer", roles_json: "[]" }];
149
+ await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
150
+ "Requires dispatch permission administer",
151
+ );
111
152
  });
112
153
 
113
154
  it("allows authenticated personal-mode administration", async () => {
114
155
  await expect(
115
156
  authorizeDispatchAdmin({}, { ...context, orgId: null }),
116
157
  ).resolves.toBeUndefined();
117
- expect(
118
- mocks.validateFederatedOrganizationMembershipForCurrentRequest,
119
- ).not.toHaveBeenCalled();
120
- expect(mocks.assertAny).not.toHaveBeenCalled();
158
+ expect(mocks.execute).not.toHaveBeenCalled();
121
159
  });
122
160
 
123
161
  it("denies an unauthenticated caller", async () => {
@@ -17,7 +17,9 @@ import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
17
17
  let dispatchAccess: AppRoles<"admin", "administer"> | undefined;
18
18
 
19
19
  function getDispatchAccess(): AppRoles<"admin", "administer"> {
20
- return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor));
20
+ return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {
21
+ allowOrgAdmins: true,
22
+ }));
21
23
  }
22
24
 
23
25
  export async function authorizeDispatchAdmin(
@@ -55,7 +57,6 @@ export async function authorizeDispatchAdmin(
55
57
  "Dispatch administration requires active organization membership.",
56
58
  );
57
59
  }
58
- if (membership.role === "owner" || membership.role === "admin") return;
59
60
  await getDispatchAccess().assertPermission(["administer"], {
60
61
  userEmail: email,
61
62
  orgId,