Skip to content

Commit a9521cb

Browse files
committed
Feat: Define governance versioned request resolution
Added - Version-aware request resolution abstractions for expected state version handling - Runtime resolver support for stale request outcomes across reject, revalidate, and renewed-approval flows - Runnable governance example for versioned resolution behavior Changed - Governance request decisions now capture stale resolution outcomes explicitly - Governance package namespace configuration now resolves folder-based namespaces without duplicate Governance segments Result Governed requests now resolve against the current state version through explicit runtime semantics instead of silent execution on drifted state. Stale requests produce documented outcomes and append decision history that explains the chosen path.
1 parent 6efe165 commit a9521cb

16 files changed

Lines changed: 451 additions & 25 deletions

Docs/Decision/Adr/ADR_023_Governance_Versioned_Request_Resolution.md

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#adr_023
55

66
## Status
7-
Proposed
7+
Accepted
88

99
## Date
1010
2026-06-21
@@ -33,19 +33,33 @@ This is a governance concern, not just a core mutation concern, because it only
3333

3434
## Decision
3535

36-
The governance package should adopt explicit version-aware request resolution semantics.
37-
38-
Expected direction:
36+
The governance package adopts explicit version-aware request resolution semantics.
3937

4038
- `MutationRequest` keeps an `ExpectedStateVersion`
41-
- request resolution must compare current state version with expected version
42-
- stale requests must not silently execute without an explicit rule
43-
- runtime resolution should choose among:
44-
- re-validate and execute against latest state
45-
- reject as stale
46-
- require renewed approval
47-
48-
The exact resolution policy is intentionally left open for the first runtime implementation.
39+
- request resolution compares current state version with expected version
40+
- stale requests do not silently execute
41+
- governance runtime resolves stale requests through one of three explicit strategies:
42+
- `RejectStale`
43+
- `RequireRenewedApproval`
44+
- `RevalidateOnLatestState`
45+
46+
Current runtime contract:
47+
48+
- matching version, or no expected version:
49+
- request receives `VersionValidated`
50+
- outcome is `ExecuteApprovedVersion`
51+
- stale request with `RejectStale`:
52+
- request becomes `Rejected`
53+
- request receives `RejectedAsStale`
54+
- stale request with `RequireRenewedApproval`:
55+
- request returns to `Pending`
56+
- `PendingReason` becomes `Approval`
57+
- `ExpectedStateVersion` is updated to the current version
58+
- request receives `RenewedApprovalRequired`
59+
- stale request with `RevalidateOnLatestState`:
60+
- request stays `Approved`
61+
- `ExpectedStateVersion` is updated to the current version
62+
- request receives `RevalidationRequired`
4963

5064
## Design Rationale
5165

@@ -57,15 +71,18 @@ The exact resolution policy is intentionally left open for the first runtime imp
5771

5872
### Positive
5973

60-
- Governance runtime will have explicit semantics for stale approvals.
74+
- Governance runtime now has explicit semantics for stale approvals.
6175
- Deferred execution becomes safer and more auditable.
76+
- Request decision history reflects stale detection and final resolution path.
6277

6378
### Negative
6479

6580
- This introduces additional policy and runtime complexity.
6681
- Different domains may want different stale resolution strategies.
82+
- Revalidation itself is still a separate runtime step beyond this version-resolution contract.
6783

6884
## Related ADRs
6985

7086
- ADR-020: Governance MutationRequest Model
7187
- ADR-021: Governance Pending Mutation Lifecycle
88+
- ADR-022: Governance Request Decisions and Storage
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using VersionedResolution.Scenarios;
2+
3+
GovernanceVersionedResolutionScenario.Run();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Governance VersionedResolution
2+
3+
This example shows how `MutationRequestVersionResolver` handles requests that were approved against an older state version.
4+
5+
It is the direct runnable example for the semantics introduced around `ExpectedStateVersion` and stale request handling.
6+
7+
## What it demonstrates
8+
9+
- resolving a request when the current state version still matches
10+
- resolving stale requests with `RejectStale`
11+
- resolving stale requests with `RequireRenewedApproval`
12+
- resolving stale requests with `RevalidateOnLatestState`
13+
- inspecting the resulting lifecycle state and appended decision history
14+
15+
## Key files
16+
17+
- [`Program.cs`](Program.cs)
18+
- [`Scenarios/GovernanceVersionedResolutionScenario.cs`](Scenarios/GovernanceVersionedResolutionScenario.cs)
19+
- [`src/Governance/Runtime/MutationRequestVersionResolver.cs`](../../../src/Governance/Runtime/MutationRequestVersionResolver.cs)
20+
- [`src/Governance/Abstractions/Resolution/VersionedRequestResolutionStrategy.cs`](../../../src/Governance/Abstractions/Resolution/VersionedRequestResolutionStrategy.cs)
21+
- [`src/Governance/Abstractions/Resolution/MutationRequestVersionResolution.cs`](../../../src/Governance/Abstractions/Resolution/MutationRequestVersionResolution.cs)
22+
23+
## Run
24+
25+
```bash
26+
dotnet run --project Examples/Governance/VersionedResolution/VersionedResolution.csproj
27+
```
28+
29+
## Expected output
30+
31+
The sample prints one block per resolution strategy and shows:
32+
33+
- selected outcome
34+
- whether the request was stale
35+
- resulting request status
36+
- updated expected version
37+
- last decision recorded during resolution
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using ModularityKit.Mutator.Abstractions.Context;
2+
using ModularityKit.Mutator.Abstractions.Intent;
3+
using ModularityKit.Mutator.Governance.Abstractions.Requests;
4+
using ModularityKit.Mutator.Governance.Abstractions.Resolution;
5+
using ModularityKit.Mutator.Governance.Runtime.Resolution;
6+
7+
namespace VersionedResolution.Scenarios;
8+
9+
internal static class GovernanceVersionedResolutionScenario
10+
{
11+
public static void Run()
12+
{
13+
var resolver = new MutationRequestVersionResolver();
14+
15+
PrintSection("Current Version Matches Expected Version");
16+
PrintResolution(
17+
resolver.Resolve(
18+
CreateApprovedRequest("v10"),
19+
currentStateVersion: "v10",
20+
resolutionContext: MutationContext.User("approver-1", "Approver One", "Current version verified"),
21+
strategy: VersionedRequestResolutionStrategy.RejectStale));
22+
23+
PrintSection("Reject Stale");
24+
PrintResolution(
25+
resolver.Resolve(
26+
CreateApprovedRequest("v10"),
27+
currentStateVersion: "v15",
28+
resolutionContext: MutationContext.User("approver-2", "Approver Two", "Reject stale request"),
29+
strategy: VersionedRequestResolutionStrategy.RejectStale));
30+
31+
PrintSection("Require Renewed Approval");
32+
PrintResolution(
33+
resolver.Resolve(
34+
CreateApprovedRequest("v10"),
35+
currentStateVersion: "v15",
36+
resolutionContext: MutationContext.User("approver-3", "Approver Three", "Request renewed approval"),
37+
strategy: VersionedRequestResolutionStrategy.RequireRenewedApproval));
38+
39+
PrintSection("Revalidate On Latest State");
40+
PrintResolution(
41+
resolver.Resolve(
42+
CreateApprovedRequest("v10"),
43+
currentStateVersion: "v15",
44+
resolutionContext: MutationContext.User("approver-4", "Approver Four", "Revalidate on the latest state"),
45+
strategy: VersionedRequestResolutionStrategy.RevalidateOnLatestState));
46+
}
47+
48+
private static MutationRequest CreateApprovedRequest(string expectedStateVersion)
49+
{
50+
return MutationRequest.Approved(
51+
stateId: "tenant-42:roles",
52+
stateType: "IamRoleState",
53+
mutationType: "GrantRoleMutation",
54+
intent: new MutationIntent
55+
{
56+
OperationName = "GrantRole",
57+
Category = "Security",
58+
Description = "Grant elevated role to tenant operator"
59+
},
60+
context: MutationContext.User("requester-1", "Requester One", "Need elevated access for incident"),
61+
expectedStateVersion: expectedStateVersion);
62+
}
63+
64+
private static void PrintSection(string title)
65+
{
66+
Console.WriteLine();
67+
Console.WriteLine($"=== {title} ===");
68+
}
69+
70+
private static void PrintResolution(MutationRequestVersionResolution resolution)
71+
{
72+
var decision = resolution.Request.Decisions[^1];
73+
74+
Console.WriteLine($"Outcome: {resolution.Outcome}");
75+
Console.WriteLine($"Was stale: {resolution.IsStale}");
76+
Console.WriteLine($"Expected version: {resolution.ExpectedStateVersion ?? "-"}");
77+
Console.WriteLine($"Current version: {resolution.CurrentStateVersion}");
78+
Console.WriteLine($"Request status: {resolution.Request.Status}");
79+
Console.WriteLine($"Next expected version: {resolution.Request.ExpectedStateVersion ?? "-"}");
80+
Console.WriteLine($"Last decision: {decision.Type}");
81+
Console.WriteLine($"Decision reason: {decision.Reason}");
82+
}
83+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\..\..\src\ModularityKit.Mutator.Governance.csproj" />
12+
</ItemGroup>
13+
14+
</Project>

src/Governance/Abstractions/Lifecycle/MutationRequestStatus.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace ModularityKit.Mutator.Governance;
1+
namespace ModularityKit.Mutator.Governance.Abstractions.Lifecycle;
22

33
/// <summary>
44
/// Represents the lifecycle status of governed mutation request.

src/Governance/Abstractions/Lifecycle/PendingMutationReason.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace ModularityKit.Mutator.Governance;
1+
namespace ModularityKit.Mutator.Governance.Abstractions.Lifecycle;
22

33
/// <summary>
44
/// Describes why a mutation request cannot execute immediately.

src/Governance/Abstractions/Requests/MutationRequest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using ModularityKit.Mutator.Abstractions.Context;
22
using ModularityKit.Mutator.Abstractions.Intent;
33
using ModularityKit.Mutator.Abstractions.Policies;
4+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle;
45

5-
namespace ModularityKit.Mutator.Governance;
6+
namespace ModularityKit.Mutator.Governance.Abstractions.Requests;
67

78
/// <summary>
89
/// Represents a governed mutation request that may execute immediately or enter a pending lifecycle.
@@ -117,7 +118,11 @@ public static MutationRequest Pending(
117118
MutationRequestDecision.Create(
118119
MutationRequestDecisionType.Submitted,
119120
context,
120-
reason: context.Reason)
121+
reason: context.Reason),
122+
MutationRequestDecision.Create(
123+
MutationRequestDecisionType.Pending,
124+
context,
125+
reason: $"Request entered pending lifecycle for reason '{pendingReason}'.")
121126
]
122127
};
123128
}

src/Governance/Abstractions/Requests/MutationRequestDecision.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using ModularityKit.Mutator.Abstractions.Context;
22

3-
namespace ModularityKit.Mutator.Governance;
3+
namespace ModularityKit.Mutator.Governance.Abstractions.Requests;
44

55
/// <summary>
66
/// Captures a single decision or lifecycle transition applied to a mutation request.
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
namespace ModularityKit.Mutator.Governance;
1+
namespace ModularityKit.Mutator.Governance.Abstractions.Requests;
22

33
/// <summary>
44
/// Represents a governance decision taken against a mutation request.
55
/// </summary>
66
public enum MutationRequestDecisionType
77
{
88
Submitted = 0,
9-
Approved = 1,
10-
Rejected = 2,
11-
Canceled = 3,
12-
Expired = 4,
13-
Superseded = 5,
14-
Executed = 6
9+
Pending = 1,
10+
Approved = 2,
11+
Rejected = 3,
12+
Canceled = 4,
13+
Expired = 5,
14+
Superseded = 6,
15+
Executed = 7,
16+
VersionValidated = 8,
17+
RevalidationRequired = 9,
18+
RenewedApprovalRequired = 10,
19+
RejectedAsStale = 11
1520
}

0 commit comments

Comments
 (0)