feat: granular RBAC with resource/operation bindings, users, groups
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / typecheck (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / package (pull_request) Has been cancelled

- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
  operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-02-23 11:05:19 +00:00
parent a6b5e24a8d
commit dcda93d179
67 changed files with 7256 additions and 498 deletions

View File

@@ -21,35 +21,44 @@ describe('CLI command registration (e2e)', () => {
expect(commandNames).toContain('apply');
expect(commandNames).toContain('create');
expect(commandNames).toContain('edit');
expect(commandNames).toContain('claude');
expect(commandNames).toContain('project');
expect(commandNames).toContain('backup');
expect(commandNames).toContain('restore');
});
it('instance command is removed (use get/delete/logs instead)', () => {
it('old project and claude top-level commands are removed', () => {
const program = createProgram();
const commandNames = program.commands.map((c) => c.name());
expect(commandNames).not.toContain('claude');
expect(commandNames).not.toContain('project');
expect(commandNames).not.toContain('instance');
});
it('claude command has config management subcommands', () => {
it('config command has claude-generate and impersonate subcommands', () => {
const program = createProgram();
const claude = program.commands.find((c) => c.name() === 'claude');
expect(claude).toBeDefined();
const config = program.commands.find((c) => c.name() === 'config');
expect(config).toBeDefined();
const subcommands = claude!.commands.map((c) => c.name());
expect(subcommands).toContain('generate');
expect(subcommands).toContain('show');
expect(subcommands).toContain('add');
expect(subcommands).toContain('remove');
const subcommands = config!.commands.map((c) => c.name());
expect(subcommands).toContain('claude-generate');
expect(subcommands).toContain('impersonate');
expect(subcommands).toContain('view');
expect(subcommands).toContain('set');
expect(subcommands).toContain('path');
expect(subcommands).toContain('reset');
});
it('project command exists with alias', () => {
it('create command has user, group, rbac subcommands', () => {
const program = createProgram();
const project = program.commands.find((c) => c.name() === 'project');
expect(project).toBeDefined();
expect(project!.alias()).toBe('proj');
const create = program.commands.find((c) => c.name() === 'create');
expect(create).toBeDefined();
const subcommands = create!.commands.map((c) => c.name());
expect(subcommands).toContain('server');
expect(subcommands).toContain('secret');
expect(subcommands).toContain('project');
expect(subcommands).toContain('user');
expect(subcommands).toContain('group');
expect(subcommands).toContain('rbac');
});
it('displays version', () => {