Skip to content

refactor: rule discovery, contract ownership, and a composition root that fits on a screen - #63

Merged
bgard68 merged 2 commits into
mainfrom
refactor/rule-discovery-and-layering
Aug 24, 2026
Merged

refactor: rule discovery, contract ownership, and a composition root that fits on a screen#63
bgard68 merged 2 commits into
mainfrom
refactor/rule-discovery-and-layering

Conversation

@bgard68

@bgard68 bgard68 commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Three findings from the architecture audit — the two structural ones, and the SRP break.

1. The rule list existed three times

Eleven registrations in the composition root, a hand-copied list in RuleCatalogue, reflection in the eval. A rule added to two of the three would never run in the third, and nothing would say so — forgetting a registry produces silence, not an error. RuleCatalogues own comment warned about this while duplicating the list.

RuleDiscovery.All() finds them in the assembly, ordered by rule id so GHA001 still registers first and output order is unchanged. All three call sites share it. Adding a rule is now a new class and nothing else — and CorpusEval already fails when a discovered rule has no fixture, so it cannot ship unmeasured either.

2. An abstraction owned by the wrong layer

IGitHubPrivateKeySource was declared in Infrastructure/GitHub/, but Program.cs injects it into the readiness endpoint — the Api layer depending on an abstraction Infrastructure owned, the only place in the project where that was true. Moved to Application/GitHubContracts.cs. No public interface remains anywhere in Infrastructure.

3. Program.cs was 944 lines

Composition root, middleware pipeline and every handler body in one file — the first file a reviewer opens.

Now 329 lines: configuration, DI, pipeline, four calls.

File Lines Serves
Program.cs 329 composition root
Endpoints/StatusEndpoints.cs 152 root, health, security and AI status
Endpoints/GitHubEndpoints.cs 243 the read-only GitHub App
Endpoints/CatalogueEndpoints.cs 61 rules and scenarios
Endpoints/WorkflowEndpoints.cs 264 analysis, remediation, explanation

Grouped by what they serve, not by verb. Nothing closed over disappeared — each group takes what it needs as a parameter, so the dependency is stated in the signature rather than captured from a file-scoped local. ValidateWorkflowRequest moves in with its only caller.

Verification

  • 204 tests pass, 0 warnings — including all 61 integration tests, which exercise these endpoints end to end
  • Route surface identical: all 18 routes diffed against main, no difference
  • No handler body changed; the split is mechanical
  • Application and Domain still contain zero using DevSecOpsSentinel.Infrastructure

bgard68 and others added 2 commits August 24, 2026 10:15
Two things the architecture nearly had.

The rule list was written out three times: eleven registrations in the
composition root, a copy in RuleCatalogue, and reflection in the eval. A rule
added to two of them would never run in the third and nothing would report it,
because forgetting a registry produces silence rather than an error — the exact
failure RuleCatalogue's own comment warned about while duplicating the list.
RuleDiscovery finds them in the assembly, ordered by rule id so GHA001 still
registers first, and all three call sites now use it.

IGitHubPrivateKeySource was declared in Infrastructure beside its
implementation, but Program.cs injects it into the readiness endpoint. That had
the outer layer depending on an abstraction the outer layer also owned — the one
place in the project where that was true. Moved to Application, where every
other contract already lives. No interfaces remain in Infrastructure.

204 tests pass. Application and Domain still reference nothing outward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…groups

944 lines held the composition root, the middleware pipeline and every handler
body at once — the first file a reviewer opens, and the least disciplined thing
in an otherwise disciplined codebase.

Program.cs is now 329 lines: configuration, DI, the pipeline, and four calls.
The handlers move to Api/Endpoints, grouped by what they serve rather than by
verb: Status (root, health, security and AI status), GitHub (the read-only App),
Catalogue (rules and scenarios), Workflow (analysis, remediation, explanation).

Nothing closed over disappeared. Each group takes what it needs as a parameter —
openAiOptions and gitHubOptions for Status, gitHubOptions for GitHub,
maximumWorkflowCharacters for Workflow, nothing for Catalogue — so the
dependency is stated in the signature rather than captured from a file-scoped
local. ValidateWorkflowRequest moves in with its only caller.

Mechanical: no handler body changed. The route surface is identical, verified by
diffing all 18 routes against main. 204 tests pass including all 61 integration
tests, which exercise these endpoints end to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bgard68 bgard68 changed the title refactor: discover the rules, and let Application own every contract refactor: rule discovery, contract ownership, and a composition root that fits on a screen Aug 24, 2026
await service.ExplainAsync(
new WorkflowDocument(
request!.FileName,
request.Content),
Comment thread src/DevSecOpsSentinel.Api/Endpoints/GitHubEndpoints.cs Dismissed
@bgard68
bgard68 merged commit 06beb8a into main Aug 24, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants