## Summary
Roll out the `duo_workflow_local_tool_governance` feature flag, which enforces human-in-the-loop AI tool governance rules for GitLab Duo agentic flows on local surfaces (IDE, CLI, Chat). When enabled, local-surface sessions resolve tool permissions against the `local_access` rule column and client-supplied agent privileges are clamped to that governance resolution. Default off; when off, behavior is byte-identical to today (web_access resolution). See https://gitlab.com/gitlab-org/gitlab/-/issues/606343+ for context. Introduced in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/249463+. The confidential security tracker gitlab#606692 can be closed once this flag is enabled.
- DRI: @dbernardi
- Team Slack channel: `#g_developer_clients`
## What could go wrong?
The flip affects all execution modes on local surfaces, not only auto mode. Manual-mode users will silently gain pre-approvals wherever a local allow rule differs from the web rules, and a local deny will block a tool regardless of execution mode. There is also a known fail-open: if governance resolution errors at the JWT-claim mint sites, denied tools resolve to empty (admin denies are dropped) rather than failing closed. This is pre-existing master behavior tracked for the re-key follow-up.
Watch Duo workflow error rates and tool-approval metrics on https://dashboards.gitlab.net during each rollout step.
## Blockers before enabling
- [ ] Re-key surface selection and the privilege clamp off the server-derived FlowExecutionAuthorizer execution classification (from https://gitlab.com/gitlab-org/gitlab/-/merge_requests/248339+) instead of the client-supplied environment, so the column choice cannot be spoofed. This must land before this flag or `duo_client_executed_flow_governance` or `duo_workflow_background_tool_governance` goes GA.
- [ ] Settle the `external` surface split-brain: on master the degraded_surface passthrough reads the `local_access` column on the creation-resolution path while the clamp and JWT mints fall back to web, so the two can resolve against different columns for the same workflow. Decide the intended external-surface behavior and make it consistent before enabling.
- [ ] **Row-derived pre-approval intersects the JWT ceiling** (ai-assist!6491 / #608968). With local `ask` rules live, a tool can still be auto-approved through the workflow row, which sits outside the ceiling - so the admin's `ask` wouldn't actually hold. This is the "one gap" I flagged on the epic.
- [ ] **Empty or errored governance claim fails closed** (#611843). An empty `tool_access_policies` claim is read as "no ceiling" today, and the mint-side case you already noted above (resolution error -\> empty `denied_tools`) drops admin denies instead of failing closed. Both need to fail closed before we lean on the token to carry local denies.
- [ ] **Token bound to its workflow** (#605431). This flag puts local `deny` into the token; while tokens are unbound a permissive one can be replayed onto a governed workflow and that deny is bypassed - so binding needs to be in effect, with client adoption, before the flip.
## Rollout
Run all production `/chatops` in [`#production`](https://gitlab.slack.com/archives/C101F3796) and cross-post results to `#g_developer_clients`.
**Non-production**
```
/chatops gitlab run feature set duo_workflow_local_tool_governance 50 --actors --dev --pre --staging --staging-ref
/chatops gitlab run feature set duo_workflow_local_tool_governance true --dev --pre --staging --staging-ref
```
**Production** — percentage rollout (wait at least 15 min between steps, watch dashboards):
```
/chatops gitlab run feature set duo_workflow_local_tool_governance <percentage> --actors
```
Or target specific actors instead:
```
/chatops gitlab run feature set --group=gitlab-org,gitlab-com duo_workflow_local_tool_governance true
/chatops gitlab run feature set --user=dbernardi duo_workflow_local_tool_governance true
```
## Before global rollout
Confirm the relevant gotchas before going to 100%, see [enabling a feature for GitLab.com](https://docs.gitlab.com/development/feature_flags/controls/#enabling-a-feature-for-gitlabcom):
- [ ] Both blockers above resolved
- [ ] [Docs + version history](https://docs.gitlab.com/development/documentation/feature_flags/) updated
- [ ] [Change management issue](https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/change-management/#feature-flags-and-the-change-management-process) opened, if required
- [ ] [External API consumers](https://docs.gitlab.com/development/feature_flags/#do-not-use-feature-flags-in-external-api-consumers) handled with a fail-open mechanism, if applicable
## Cleanup
Remove the flag once [deemed stable](https://handbook.gitlab.com/handbook/product-development/how-we-work/product-development-flow/feature-flag-lifecycle/#feature-flag-lifecycle). Remove the flag and its YAML definition from the codebase, then:
```
/chatops gitlab run release check <merge-request-url> <milestone>
/chatops gitlab run feature delete duo_workflow_local_tool_governance --dev --pre --staging --staging-ref --production
```
## Rollback
```
/chatops gitlab run feature set duo_workflow_local_tool_governance false # production
/chatops gitlab run feature set duo_workflow_local_tool_governance false --dev --pre --staging --staging-ref # non-production
/chatops gitlab run feature delete duo_workflow_local_tool_governance --dev --pre --staging --staging-ref --production # remove entirely
```