Files
windmill/frontend/src/lib/editRights.test.ts
T
hugocasaandClaude Opus 5.5 e5dedd147e feat: let flow builder operators build AI agents (#11898)
* 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>
2026-10-23 15:05:20 +02:00

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)
})
})