feat: per-role fork capability and fork role mapping on rulesets (#12054)

* feat: fork capability per role and fork role mapping on rulesets

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep unset fork for unrestricted roles and apply fork role mapping to fork-owner adds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: fork_roles feature usage telemetry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: derive an unset fork in the role editor as the server does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: update ee-repo-ref to d535972f0b35c6a7f257720816bdaec18465adef

This commit updates the EE repository reference after PR #929 was merged in windmill-ee-private.

Previous ee-repo-ref: 4a8d59cd1563382fcec85b1367f7e15e09bc13c2

New ee-repo-ref: d535972f0b35c6a7f257720816bdaec18465adef

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
Ruben Fiszel
2026-10-28 00:43:33 +01:00
committed by GitHub
co-authored by Claude Opus 5.5 windmill-internal-app[bot]
parent e3b4dcffbc
commit ca8f0a16c1
43 changed files with 877 additions and 232 deletions
+2 -2
View File
@@ -4,13 +4,13 @@
anonymous usage-stats payload. It answers "does anyone use this, and which variant do they pick"
without any identifying data leaving the instance.
It currently carries 86 registered actions across thirty-three features (`ai_session`, `ai_chat`,
It currently carries 89 registered actions across thirty-four features (`ai_session`, `ai_chat`,
`ai_fix`, `ai_agent`, `ai_decision`, `ai_agent_eval`, `app_sandbox`, `datatable`, `db_manager`, `flow_editor`,
`flow_run`, `flow_step`, `home`, `header_nav`, `run_form`, `debugger`, `trigger`, `command_script`,
`mcp`, `mcp_workspace`, `hub_script`, `usage_meter`, `sso_groups_claim`, `cloud_trial_offer`,
`job_token_scopes`, `worker_draining`, `worker_group`, `worker_resource_stats`,
`dev_workspace_isolation`, `slack_agent`, `variable_expiration`, `anthropic_federation`,
`deployment_request`). Nearly all of the
`deployment_request`, `fork_roles`). Nearly all of the
product is uninstrumented, so new user-facing work is the opportunity to change that.
## When to instrument
+15
View File
@@ -19,6 +19,7 @@ code is `windmill_common::roles`.
| `flows` | `none` / `compose` / `full` | yes; also AI agents, whose tools are checked as composed steps |
| `apps` | `none` / `compose` / `full` | yes; low-code apps and worker-built apps need `full` |
| `manage_schedules`, `manage_triggers` | bool | yes, see `docs/operator-write-rights.md` |
| `fork` | bool | yes, at both fork routes; unset by every layer, it is granted exactly to an unrestricted role |
| `visibility.*` | bool per page | no, UI only |
`compose` is the former operator builder right: the composition-only checks of
@@ -33,6 +34,20 @@ deleting one that a member, invite, auto-invite or instance group mapping still
and so is deleting an instance role that a workspace still overrides (the override would fall onto
`base_for` instead of the definition it was written against).
## Forks
A member's role carries into a fork: the forker, and every member `copy_members` or
`add_admins_and_developers_to_forks` brings, joins the fork with the role they hold in the parent,
and the fork gets the parent's `role_overrides` so the name means the same there. A protection
ruleset's `fork_role_mapping` (`{ role here: role in the fork }`) replaces that role; where several
rulesets map one role, the first by name wins. A role named by a mapping cannot be deleted. A
fork's non-admin creator may add a parent member only when the mapping makes them a developer, so
re-adding cannot undo a mapping that restricts.
`fork`, like `visibility`, does not make a role restricted: withdrawing it from `developer` must not
turn every developer into an operator for the raw `is_operator` checks. So the fork routes read it
from the member's role (`caller_can_fork`), never through `caller_capabilities`' fast path.
## Who is restricted
`ApiAuthed::is_operator` means "this member's role is restricted", computed at authentication from