mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 00:03:09 +00:00
* feat: let flow builder operators build AI agents Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check linked agents' bound tools and type, validate on the write's transaction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: bound the linked-agent walk by sorting bound tool ids Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let builders bind only the agent tool inputs left to the model Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check every agent tool sharing a bound id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: show builders the agent's own tool inputs read-only and name agents in the setting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: show the agent's own tool inputs with the read-only step viewer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: give the agent-set inputs heading the input label style Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: label the agent's fixed tool inputs static inputs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: address cubic review on agent builder rights Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check agent writes against the role's flows capability Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: name the flows capability as the agent builder right Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: gate agent evals on the role's code capability Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: move the composed-flow check into windmill-api-auth Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { RoleCapabilities } from '$lib/gen'
|
|
import type { UserExt } from '$lib/stores'
|
|
import { FULL_CAPABILITIES, legacyOperatorCapabilities } from '$lib/roleCapabilities'
|
|
import { canEditItem, roleCanAuthor, roleCanDraft } from './editRights'
|
|
|
|
function user(overrides: Partial<UserExt>): UserExt {
|
|
return {
|
|
email: 'op@windmill.dev',
|
|
username: 'op',
|
|
name: 'op',
|
|
is_admin: false,
|
|
is_super_admin: false,
|
|
created_at: '',
|
|
groups: [],
|
|
pgroups: [],
|
|
folders: [],
|
|
folders_owners: [],
|
|
folders_read: [],
|
|
operator: true,
|
|
...overrides
|
|
} as UserExt
|
|
}
|
|
|
|
const role = (overrides: Partial<RoleCapabilities>): RoleCapabilities => ({
|
|
...legacyOperatorCapabilities(undefined),
|
|
...overrides
|
|
})
|
|
|
|
describe('edit rights', () => {
|
|
it('lets a compose-only flow role author flows and agents and nothing else', () => {
|
|
const builder = role({ flows: 'compose' })
|
|
expect(roleCanAuthor('flow', builder)).toBe(true)
|
|
expect(roleCanAuthor('agent', builder)).toBe(true)
|
|
expect(roleCanDraft('agent', builder)).toBe(true)
|
|
expect(roleCanAuthor('agent', role({ apps: 'compose' }))).toBe(false)
|
|
expect(roleCanAuthor('script', builder)).toBe(false)
|
|
expect(roleCanAuthor('raw_app', builder)).toBe(false)
|
|
expect(roleCanDraft('raw_app', builder)).toBe(false)
|
|
expect(roleCanDraft('raw_app', role({ apps: 'compose' }))).toBe(true)
|
|
})
|
|
|
|
// apps.rs `require_full_apps`: a composing role builds full-code apps, never low-code ones.
|
|
it('keeps low-code apps for full app authors', () => {
|
|
expect(roleCanAuthor('app', role({ apps: 'compose' }))).toBe(false)
|
|
expect(roleCanAuthor('raw_app', role({ apps: 'compose' }))).toBe(true)
|
|
const scripter = { ...FULL_CAPABILITIES, apps: 'none' as const }
|
|
expect(roleCanAuthor('script', scripter)).toBe(true)
|
|
expect(roleCanAuthor('app', scripter)).toBe(false)
|
|
expect(roleCanAuthor('raw_app', scripter)).toBe(false)
|
|
})
|
|
|
|
// drafts.rs admits a restricted role's draft of a kind it could deploy, and no other.
|
|
it('lets a role draft schedules and triggers until the right is withdrawn', () => {
|
|
expect(roleCanDraft('schedule', role({}))).toBe(true)
|
|
expect(roleCanDraft('trigger', role({}))).toBe(true)
|
|
expect(roleCanDraft('schedule', role({ manage_schedules: false }))).toBe(false)
|
|
expect(roleCanDraft('trigger', role({ manage_triggers: false }))).toBe(false)
|
|
})
|
|
|
|
it('lets any role draft resources and variables, but scripts only with code', () => {
|
|
expect(roleCanDraft('resource', role({}))).toBe(true)
|
|
expect(roleCanDraft('variable', role({}))).toBe(true)
|
|
expect(roleCanDraft('script', role({}))).toBe(false)
|
|
expect(roleCanDraft('script', role({ code: true }))).toBe(true)
|
|
})
|
|
|
|
// The role alone is not enough: a builder still cannot edit a flow in a folder they only read.
|
|
it('requires write access to the item on top of the role', () => {
|
|
const builder = role({ flows: 'compose' })
|
|
expect(canEditItem('flow', 'f/demo/sum', {}, user({}), builder)).toBe(false)
|
|
expect(canEditItem('flow', 'u/op/sum', {}, user({}), builder)).toBe(true)
|
|
})
|
|
})
|