feat: let tenants configure executor approval gates via the API - #1
Merged
Merged
Conversation
Executor nodes default to autonomous, as before. Tenants can now inspect a
workflow version's nodes and set, per executor, whether it runs autonomously
or waits for approval, without a redeploy.
- GET/PUT/DELETE /workflows/{name}/versions/{version}/nodes[/{executorId}]
list nodes with their declared, tenant, and effective gates, and write or
drop the calling tenant's policy. Bulk writes are all-or-nothing.
- IGatePolicyStore gains a tenant dimension (null = host-wide) plus List and
Remove; the runner resolves gates for instance.TenantId. Precedence is
instance override, tenant policy, host policy, definition, autonomous.
- ApprovalGate.Locked marks a declared gate as the author's floor. Tenant
config may tighten it; weakening is refused with 409 and re-tightened on
read, so a policy stored by another route cannot un-gate an executor.
- WorkflowInspector builds a definition once per version against an
inspection context to read its nodes; results are cached.
Also fixes SqlServerGatePolicyStore.FindAsync returning only the
instance-scoped key instead of falling through to the workflow scope, and
marks ApprovalGate.Predicate [JsonIgnore] - that store round-trips gates
through JsonSerializer, which would have thrown on the delegate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces per-tenant configuration of executor approval gates via a new API surface, allowing tenants to override (or remove) approval requirements for specific workflow executors at runtime, while preserving author-declared “locked” gate floors. It also extends gate policy storage to be tenant-aware (with host-wide fallback), adds workflow graph inspection/caching to support node discovery, and updates tests/docs accordingly.
Changes:
- Add node-configuration endpoints (
GET/PUT/DELETE /workflows/{name}/versions/{version}/nodes...) backed byGateConfigurationService. - Extend
IGatePolicyStorewith a tenant dimension plusListAsync/RemoveAsync, and update runner gate resolution to useinstance.TenantId. - Add locked-gate reconciliation rules (
GatePolicyRules) and workflow node inspection (WorkflowInspector) with caching.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Abacus.Run.UnitTests/GateEvaluatorTests.cs | Extends evaluator tests for tenant-vs-host precedence and locked-gate behavior. |
| tests/Abacus.Run.UnitTests/GateConfigurationTests.cs | Adds unit coverage for node discovery, policy validation, locked-floor enforcement, auditing, and version scoping. |
| tests/Abacus.Run.UnitTests/CheckpointAndStoreTests.cs | Updates/extends in-memory gate policy store tests for tenant scoping, listing, and removal fallback. |
| tests/Abacus.Run.IntegrationTests/TenantGateConfigurationTests.cs | Adds integration coverage for node configuration API and tenant-scoped runtime effects. |
| tests/Abacus.Run.IntegrationTests/HostFixture.cs | Registers a tenant-configurable workflow and adds tenant header support to the test start helper. |
| src/Abacus.Run/Persistence/InMemoryStores.cs | Updates InMemoryGatePolicyStore to support tenant scope + list/remove and host fallback. |
| src/Abacus.Run/Core/WorkflowRunner.cs | Passes instance.TenantId into GateEvaluator for tenant-scoped resolution. |
| src/Abacus.Run/Core/WorkflowInspector.cs | New inspector that builds workflow definitions in an inspection context and caches node descriptors. |
| src/Abacus.Run/Core/Stores.cs | Updates IGatePolicyStore contract with tenant dimension + list/remove semantics. |
| src/Abacus.Run/Core/GatePolicyRules.cs | New logic to validate and reconcile tenant policies against locked author declarations. |
| src/Abacus.Run/Core/GateEvaluator.cs | Updates gate precedence to include tenant + host scopes and applies locked-floor reconciliation. |
| src/Abacus.Run/Api/HostBuilderExtensions.cs | Registers WorkflowInspector and GateConfigurationService in DI. |
| src/Abacus.Run/Api/GateConfigurationService.cs | Implements node projection, validation, all-or-nothing writes, and audit logging. |
| src/Abacus.Run/Api/Endpoints.cs | Maps node-configuration endpoints and adds GateConfigResult → HTTP result translation. |
| src/Abacus.Run/Abstractions/WorkflowDefinition.cs | Introduces WorkflowNodeDescriptor and records nodes during workflow build (incl. raw nodes). |
| src/Abacus.Run/Abstractions/Approvals.cs | Adds ApprovalGate.Locked and marks Predicate as [JsonIgnore]. |
| src/Abacus.Run.Service/Infrastructure/SqlServerInfrastructureStores.cs | Extends SQL gate policy store to support tenant/host scoping and list/remove. |
| README.md | Documents gate declaration and new tenant node-configuration API surface. |
| docs/wiki.md | Adds design/operational documentation for tenant executor configuration, precedence, and locked floors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+250
to
+252
| /// <summary>Host-wide policies are stored under a scope no tenant id can collide with.</summary> | ||
| private const string HostScope = " |
Executor nodes default to autonomous, as before. Tenants can now inspect a workflow version's nodes and set, per executor, whether it runs autonomously or waits for approval, without a redeploy.
Also fixes SqlServerGatePolicyStore.FindAsync returning only the instance-scoped key instead of falling through to the workflow scope, and marks ApprovalGate.Predicate [JsonIgnore] - that store round-trips gates through JsonSerializer, which would have thrown on the delegate.