Skip to content

Commit 6333bfd

Browse files
authored
Feat: Add Redis governance provider (#39)
## Summary This PR implements the `ModularityKit.Mutator.Governance.Redis` provider from scratch, bringing fully functioning, Redis backed governance storage implementation to ecosystem. It also includes comprehensive documentation and runnable examples. ## Added - complete Redis governance provider implementation - centralized Redis keyspace conventions and serialization strategy - document and identifier set loading for efficient read side operations - candidate selection and execution for distributed orchestration - write side request persistence mechanism - dedicated package README for `ModularityKit.Mutator.Governance.Redis` - `RedisQueries` runnable example demonstrating Redis backed governance queries - `docker-compose.yml` for local Redis testing in the examples ## Result The repository now provides fully implemented, Redis backed governance provider. It correctly documents its capabilities, provides runnable example for query workflows using Redis, and has accurate package descriptions in the root overview. The new package is fully implemented, closing out all underlying provider tasks. ## Testing - verified local build - verified `RedisQueries` example runs with local Redis container ## Linked Issues - Closes #38 - Closes #37 - Closes #36 - Closes #35 - Closes #34 - Closes #33 - Closes #32 - Closes #31 - Closes #20 - Closes #8 ## Checklist - [x] Redis provider features are implemented - [x] Redis package has dedicated package README - [x] Redis governance queries example is provided - [x] Root README correctly describes all packages
2 parents aabec38 + 1222929 commit 6333bfd

83 files changed

Lines changed: 4780 additions & 118 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish-artifacts.yml

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ on:
77
description: "Optional package version, usually the release tag without the leading v."
88
required: false
99
type: string
10+
core_version:
11+
description: "Optional manual version for ModularityKit.Mutator."
12+
required: false
13+
type: string
14+
governance_version:
15+
description: "Optional manual version for ModularityKit.Mutator.Governance."
16+
required: false
17+
type: string
18+
redis_version:
19+
description: "Optional manual version for ModularityKit.Mutator.Governance.Redis."
20+
required: false
21+
type: string
1022

1123
permissions:
1224
contents: read
@@ -28,37 +40,69 @@ jobs:
2840
- name: Restore
2941
run: dotnet restore ModularityKit.Mutator.slnx
3042

31-
- name: Resolve package version
43+
- name: Resolve package versions
3244
id: version
3345
env:
3446
PACKAGE_VERSION: ${{ inputs.package_version }}
47+
CORE_VERSION: ${{ inputs.core_version }}
48+
GOVERNANCE_VERSION: ${{ inputs.governance_version }}
49+
REDIS_VERSION: ${{ inputs.redis_version }}
3550
REF_NAME: ${{ github.ref_name }}
3651
run: |
37-
version="$PACKAGE_VERSION"
38-
if [ -z "$version" ]; then
39-
version="$REF_NAME"
40-
fi
41-
version="${version#v}"
42-
if ! printf '%s' "$version" | grep -Eq '^[0-9]+(\.[0-9]+){1,2}([-+][0-9A-Za-z.-]+)?$'; then
43-
version="0.1.0"
44-
fi
45-
echo "package_version=$version" >> "$GITHUB_OUTPUT"
52+
resolve_version() {
53+
local value="$1"
54+
local fallback="$2"
55+
56+
if [ -z "$value" ]; then
57+
value="$fallback"
58+
fi
59+
60+
if [ -z "$value" ]; then
61+
value="$REF_NAME"
62+
fi
63+
64+
value="${value#v}"
65+
66+
if ! printf '%s' "$value" | grep -Eq '^[0-9]+(\.[0-9]+){1,2}([-+][0-9A-Za-z.-]+)?$'; then
67+
value="0.1.0"
68+
fi
69+
70+
printf '%s' "$value"
71+
}
72+
73+
shared_version="$(resolve_version "$PACKAGE_VERSION" "")"
74+
core_version="$(resolve_version "$CORE_VERSION" "$shared_version")"
75+
governance_version="$(resolve_version "$GOVERNANCE_VERSION" "$shared_version")"
76+
redis_version="$(resolve_version "$REDIS_VERSION" "$shared_version")"
77+
78+
echo "package_version=$shared_version" >> "$GITHUB_OUTPUT"
79+
echo "core_version=$core_version" >> "$GITHUB_OUTPUT"
80+
echo "governance_version=$governance_version" >> "$GITHUB_OUTPUT"
81+
echo "redis_version=$redis_version" >> "$GITHUB_OUTPUT"
4682
4783
- name: Pack core package
4884
run: >
4985
dotnet pack src/ModularityKit.Mutator.csproj
5086
-c Release
5187
--no-restore
5288
-o nupkg
53-
-p:PackageVersion=${{ steps.version.outputs.package_version }}
89+
-p:PackageVersion=${{ steps.version.outputs.core_version }}
5490
5591
- name: Pack governance package
5692
run: >
5793
dotnet pack src/ModularityKit.Mutator.Governance.csproj
5894
-c Release
5995
--no-restore
6096
-o nupkg
61-
-p:PackageVersion=${{ steps.version.outputs.package_version }}
97+
-p:PackageVersion=${{ steps.version.outputs.governance_version }}
98+
99+
- name: Pack Redis governance package
100+
run: >
101+
dotnet pack src/Redis/ModularityKit.Mutator.Governance.Redis.csproj
102+
-c Release
103+
--no-restore
104+
-o nupkg
105+
-p:PackageVersion=${{ steps.version.outputs.redis_version }}
62106
63107
- name: Upload packages
64108
uses: actions/upload-artifact@v4

.github/workflows/publish-packages.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ on:
77
description: Release version without the leading "v"
88
required: false
99
type: string
10+
core_version:
11+
description: Optional manual version for ModularityKit.Mutator
12+
required: false
13+
type: string
14+
governance_version:
15+
description: Optional manual version for ModularityKit.Mutator.Governance
16+
required: false
17+
type: string
18+
redis_version:
19+
description: Optional manual version for ModularityKit.Mutator.Governance.Redis
20+
required: false
21+
type: string
1022
publish_nuget:
1123
description: Publish to NuGet.org
1224
required: false
@@ -23,6 +35,18 @@ on:
2335
description: Release version without the leading "v"
2436
required: false
2537
type: string
38+
core_version:
39+
description: Optional manual version for ModularityKit.Mutator
40+
required: false
41+
type: string
42+
governance_version:
43+
description: Optional manual version for ModularityKit.Mutator.Governance
44+
required: false
45+
type: string
46+
redis_version:
47+
description: Optional manual version for ModularityKit.Mutator.Governance.Redis
48+
required: false
49+
type: string
2650
publish_nuget:
2751
description: Publish to NuGet.org
2852
required: false
@@ -41,6 +65,9 @@ jobs:
4165
uses: ./.github/workflows/publish-artifacts.yml
4266
with:
4367
package_version: ${{ inputs.version }}
68+
core_version: ${{ inputs.core_version }}
69+
governance_version: ${{ inputs.governance_version }}
70+
redis_version: ${{ inputs.redis_version }}
4471

4572
publish-nuget:
4673
name: Publish to NuGet.org
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# ADR-029: Governance Redis Provider Package
2+
3+
## Tag
4+
#adr_029
5+
6+
## Status
7+
Accepted
8+
9+
## Date
10+
2026-06-25
11+
12+
## Scope
13+
ModularityKit.Mutator.Governance.Redis
14+
15+
## Context
16+
17+
The governance package already defines:
18+
19+
- durable mutation requests
20+
- optimistic concurrency around request revisions
21+
- request, approval, and decision query contracts
22+
- an in-memory implementation suitable for tests and examples
23+
24+
What remained open was a first persistence provider that can move governance state beyond in-memory storage without coupling `ModularityKit.Mutator.Governance` to a database-specific implementation.
25+
26+
The first provider needs to support:
27+
28+
- durable request document storage
29+
- optimistic concurrency for request updates
30+
- query-oriented reads over governed request data
31+
- simple application integration through DI
32+
33+
At the same time, this should remain separate from the governance abstractions package so provider-specific concerns do not leak into the base runtime API.
34+
35+
## Decision
36+
37+
Introduce a dedicated package:
38+
39+
- `ModularityKit.Mutator.Governance.Redis`
40+
41+
The provider should:
42+
43+
- implement `IMutationRequestStore`
44+
- implement `IMutationRequestQueryStore`
45+
- register through dedicated DI extensions
46+
- keep all Redis-specific logic inside the provider package
47+
- reuse governance query contracts instead of inventing a Redis-specific query surface
48+
49+
The provider should remain additive:
50+
51+
- `ModularityKit.Mutator.Governance` owns contracts and runtime semantics
52+
- `ModularityKit.Mutator.Governance.Redis` owns Redis persistence and Redis-specific internal read/write mechanics
53+
54+
## Design Rationale
55+
56+
- Governance contracts should stay provider-neutral.
57+
- Redis is a pragmatic first persistence backend for request-oriented workflows with simple document state and indexable queue views.
58+
- Package separation allows future providers such as EF Core or PostgreSQL without changing governance abstractions.
59+
- DI registration gives applications a low-friction way to switch from in-memory storage to Redis-backed storage.
60+
61+
## Consequences
62+
63+
### Positive
64+
65+
- Governance gets a real persistence provider beyond in-memory storage.
66+
- Redis-backed request and query implementations can evolve independently from governance contracts.
67+
- Future providers now have a clear packaging precedent.
68+
- Examples and tests can exercise a provider-backed read side without changing core governance APIs.
69+
70+
### Negative
71+
72+
- Provider package maintenance adds another surface to version and test.
73+
- Redis-specific internal design decisions must now be documented and kept coherent over time.
74+
- Query behavior remains contract-compatible but may have different performance characteristics than future relational providers.
75+
76+
## Related ADRs
77+
78+
- ADR-019: Governance Package Separation
79+
- ADR-022: Governance Request Decisions and Storage
80+
- ADR-026: Governance Request Query API
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# ADR-030: Governance Redis Request Storage and Query Strategy
2+
3+
## Tag
4+
#adr_030
5+
6+
## Status
7+
Accepted
8+
9+
## Date
10+
2026-06-25
11+
12+
## Scope
13+
ModularityKit.Mutator.Governance.Redis
14+
15+
## Context
16+
17+
Once the Redis provider package exists, it still needs a concrete storage and read strategy.
18+
19+
Governed request data has two competing needs:
20+
21+
- writes should stay simple and durable
22+
- queue-oriented operational reads should avoid a full scan of every request whenever possible
23+
24+
The provider also needs to preserve governance runtime semantics such as:
25+
26+
- optimistic concurrency by request revision
27+
- storage-agnostic request query filtering
28+
- approval and decision projections built from parent request state
29+
30+
Without an explicit strategy, the Redis provider could drift into ad hoc key naming, inconsistent indexing, or duplicated query behavior that no longer matches the governance abstractions.
31+
32+
## Decision
33+
34+
The Redis provider stores one serialized request document per request and maintains a small set of Redis secondary indexes for common request-oriented reads.
35+
36+
Storage shape:
37+
38+
- one request JSON document per `MutationRequest`
39+
- one revision key per request for optimistic concurrency
40+
- set indexes for:
41+
- all request ids
42+
- requests by `StateId`
43+
- requests by `MutationRequestStatus`
44+
- all pending requests
45+
- pending requests by `PendingMutationReason`
46+
47+
Query shape:
48+
49+
- Redis index selection happens first through candidate-planning internals
50+
- matching request documents are then loaded in bulk
51+
- final filtering is applied through governance query evaluators, not Redis-specific ad hoc logic
52+
- approval views and decision views are projected from loaded parent requests after candidate selection
53+
54+
Internal provider structure should remain decomposed:
55+
56+
- candidate planning and execution
57+
- document key creation and payload loading
58+
- document materialization
59+
- read-side query orchestration
60+
61+
## Design Rationale
62+
63+
- Document-per-request storage maps naturally to the governance request model.
64+
- Separate revision keys give a simple optimistic concurrency mechanism in Redis transactions.
65+
- A small set of explicit secondary indexes improves the common queue and status reads without forcing the provider into a large custom indexing subsystem.
66+
- Reusing governance evaluators keeps provider behavior aligned with in-memory and future providers.
67+
- Internal decomposition makes Redis-specific read mechanics easier to evolve without turning one class into the entire provider.
68+
69+
## Consequences
70+
71+
### Positive
72+
73+
- Request writes stay simple and explicit.
74+
- Common operational views such as pending queues and state/status slices can be narrowed through Redis sets.
75+
- Query semantics remain aligned with governance abstractions because the final filter pass is evaluator-driven.
76+
- Internal provider responsibilities are easier to test and evolve independently.
77+
78+
### Negative
79+
80+
- Broad ad hoc queries still fall back to loading candidate request documents and filtering in memory.
81+
- Index maintenance increases write-path complexity compared to pure document storage.
82+
- Additional indexes may be needed later for higher-volume provider scenarios.
83+
84+
## Related ADRs
85+
86+
- ADR-022: Governance Request Decisions and Storage
87+
- ADR-026: Governance Request Query API
88+
- ADR-029: Governance Redis Provider Package
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ADR-031: Governance Redis Serialization and Document Compatibility
2+
3+
## Tag
4+
#adr_031
5+
6+
## Status
7+
Accepted
8+
9+
## Date
10+
2026-06-25
11+
12+
## Scope
13+
ModularityKit.Mutator.Governance.Redis
14+
15+
## Context
16+
17+
The Redis provider persists governed mutation requests as serialized documents.
18+
19+
That creates a compatibility boundary:
20+
21+
- request data must round-trip without losing governance semantics
22+
- read models must tolerate heterogeneous metadata values
23+
- provider internals must avoid a Redis-specific domain model fork
24+
- future package versions should evolve the serialized shape deliberately, not accidentally
25+
26+
The governance request model already contains nested structures such as:
27+
28+
- mutation intent
29+
- request metadata
30+
- approval requirements
31+
- decision history
32+
- version resolution state
33+
34+
Without an explicit serialization decision, the provider could drift into:
35+
36+
- inconsistent JSON shapes across components
37+
- fragile metadata handling for object-valued entries
38+
- hidden coupling between runtime classes and Redis payload format
39+
40+
## Decision
41+
42+
The Redis provider serializes the existing governance request model directly as JSON documents and treats that JSON shape as the persisted document contract for the provider.
43+
44+
The provider should:
45+
46+
- serialize full `MutationRequest` documents rather than introduce a parallel storage DTO graph
47+
- keep provider serialization centralized in dedicated Redis serialization components
48+
- support heterogeneous metadata values through explicit converter handling
49+
- keep document materialization inside provider internals, not spread across query code paths
50+
- evolve document shape through deliberate package changes backed by ADRs when compatibility semantics change
51+
52+
## Design Rationale
53+
54+
- Reusing the governance model avoids translation layers that would duplicate request, approval, and decision semantics.
55+
- Centralized serialization keeps Redis persistence mechanics consistent across reads and writes.
56+
- Explicit converter support is necessary because governance metadata is intentionally flexible and may contain inferred object values.
57+
- A single persisted document contract makes provider behavior easier to reason about in tests, examples, and future migrations.
58+
59+
## Consequences
60+
61+
### Positive
62+
63+
- Redis persistence stays aligned with governance runtime semantics.
64+
- Serialization behavior is easier to test because all provider paths use the same document contract.
65+
- Metadata and nested governance structures can round-trip without ad hoc per-query parsing.
66+
- Future compatibility changes now have an explicit decision boundary.
67+
68+
### Negative
69+
70+
- The provider is coupled to the serialized shape of the governance request model.
71+
- Backward-compatible evolution requires discipline when changing serialized request members.
72+
- JSON document size grows with request history and approval detail.
73+
74+
## Related ADRs
75+
76+
- ADR-022: Governance Request Decisions and Storage
77+
- ADR-029: Governance Redis Provider Package
78+
- ADR-030: Governance Redis Request Storage and Query Strategy

0 commit comments

Comments
 (0)