Skip to content

test(arch): seam-surface guard, no persistence type crosses a seam (#847) - #875

Merged
mforce merged 5 commits into
mainfrom
chore/847-seam-surface
Sep 15, 2026
Merged

mforce merged 5 commits into
mainfrom
chore/847-seam-surface

Conversation

@mforce

@mforce mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Closes #847

Slice 5 of #514 (modular monolith), Track B. Enforcement only. No src/ file, no CI change, no package. Independent of #872 in code; it lands on main directly.

What this adds

  • tests/Cluckwork.Application.Tests/Architecture/SeamSurfaceScanner.cs — reflection over every public interface under Cluckwork.Application.Features.* and Cluckwork.Application.Common, recursing through parameters, return types, generic arguments, arrays, byref and the public properties of any Cluckwork.* type it reaches. Forbidden, as a first-match table: DbContext and subclasses, DbSet<>, IQueryable / IQueryable<> / IOrderedQueryable<>, any Microsoft.EntityFrameworkCore type, the bases Entity<> and AggregateRoot<> as a declared type, any Cluckwork.Infrastructure type. A second leg pins that the Application assembly references no Microsoft.EntityFrameworkCore*, Npgsql or Cluckwork.Infrastructure assembly, which is what keeps the first leg's DbSet/DbContext rows from being vacuous today.
  • SeamSurfaceTests.cs — one fixture interface per rule in its own sub-namespace, one named assertion each, three green controls (concrete aggregate, PagedResult<T>, Money), and the floor test. SeamSurfaceRealAssemblyTests is the gate: 35 interfaces inspected today, floor 30, zero violations.
  • docs/decisions/847-seam-surface-guard.md and one AGENTS.md paragraph under Application shape.

Why now

Zero IQueryable returns in Application today and no EF package, so the guard starts with no exemptions. Its job is the day someone adds Task<IQueryable<Expense>> Query() because it made one call site shorter. Track B is nominally gated on #788/#789 for rebase reasons in Program.cs; a test-only guard never touches that file.

Mutation evidence (each recorded red, then reverted before commit)

# Mutation Red line
1 IQueryable<Flock> Query(); on IFlockRepository (with a throwaway stub on FlockRepository so the solution compiles far enough to run the test) IFlockRepository.Query exposes System.Linq.IQueryable1[[Cluckwork.Domain.Flocks.Flock…]] via IQueryable`
2 Microsoft.EntityFrameworkCore added to Cluckwork.Application.csproj, lock regenerated with --force-evaluate, DbSet<Expense> Set(); on IFlockRepository both real-assembly tests red: Cluckwork.Application references Microsoft.EntityFrameworkCore and IFlockRepository.Set exposes Microsoft.EntityFrameworkCore.DbSet1[[…Expense…]] via DbSet`
3 Microsoft.EntityFrameworkCore added to the csproj without regenerating the lock error NU1004 … restore can't be run in locked mode (the fence in front of the guard)
4 Green control: Task<Money> Total(); on a repository interface Passed! 2/2

The issue's DbSet/AppDbContext-in-src/ mutations cannot compile as written (Application has no EF reference), which is exactly what leg 2 pins; mutation 2 is their compilable form.

Verification

  • dotnet build Cluckwork.sln: 0 warnings.
  • Cluckwork.Application.Tests: 304 passed (13 in SeamSurface*), Cluckwork.Domain.Tests: 491 passed.
  • Documentation tests (19) and SchemaDocsTests (4) green on the new markdown.
Mutation outputs

01-iqueryable-flock-repository.txt

Determining projects to restore...
  All projects are up-to-date for restore.
  Cluckwork.Domain -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Domain/bin/Debug/net10.0/Cluckwork.Domain.dll
  Cluckwork.Application -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Application/bin/Debug/net10.0/Cluckwork.Application.dll
  Cluckwork.Infrastructure -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Infrastructure/bin/Debug/net10.0/Cluckwork.Infrastructure.dll
  Cluckwork.Application.Tests -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/tests/Cluckwork.Application.Tests/bin/Debug/net10.0/Cluckwork.Application.Tests.dll
Test run for /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/tests/Cluckwork.Application.Tests/bin/Debug/net10.0/Cluckwork.Application.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.2 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.26]     Cluckwork.Application.Tests.Architecture.SeamSurfaceRealAssemblyTests.RealApplicationAssembly_NoPublicInterfaceExposesPersistence [FAIL]
  Failed Cluckwork.Application.Tests.Architecture.SeamSurfaceRealAssemblyTests.RealApplicationAssembly_NoPublicInterfaceExposesPersistence [12 ms]
  Error Message:
   seam-surface guard failed:
  Cluckwork.Application.Features.Flocks.IFlockRepository.Query expo

02-dbset-with-ef-package.txt

Failed Cluckwork.Application.Tests.Architecture.SeamSurfaceRealAssemblyTests.RealApplicationAssembly_DoesNotReferenceEntityFrameworkOrInfrastructure [3 ms]
  Cluckwork.Application references Microsoft.EntityFrameworkCore — Application must stay free of EF/Npgsql/Infrastructure so the reflection walk's DbSet/DbContext rules are not vacuous
  Failed Cluckwork.Application.Tests.Architecture.SeamSurfaceRealAssemblyTests.RealApplicationAssembly_NoPublicInterfaceExposesPersistence [12 ms]
  Cluckwork.Application.Features.Flocks.IFlockRepository.Set exposes Microsoft.EntityFrameworkCore.DbSet`1[[Cluckwork.Domain.Expenses.Expense, Cluckwork.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] via DbSet<Expense>
Failed!  - Failed:     2, Passed:     0, Skipped:     0, Total:     2, Duration: 31 ms - Cluckwork.Application.Tests.dll (net10.0)

03-ef-packageref-restore.txt

Determining projects to restore...
/home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Application/Cluckwork.Application.csproj : error NU1004: The package references have changed for net10.0. Lock file's package references: FluentValidation:[12.*, ), Microsoft.Extensions.Logging.Abstractions:[10.*, ), Riok.Mapperly:[4.*, ), project's package references: FluentValidation >= 12.*, Microsoft.EntityFrameworkCore >= 10.*, Microsoft.Extensions.Logging.Abstractions >= 10.*, Riok.Mapperly >= 4.*.The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file.
  Restored /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Domain/Cluckwork.Domain.csproj (in 91 ms).
  Failed to restore /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Application/Cluckwork.Application.csproj (in 91 ms).

04-green-control-money-return.txt

Determining projects to restore...
  All projects are up-to-date for restore.
  Cluckwork.Domain -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Domain/bin/Debug/net10.0/Cluckwork.Domain.dll
  Cluckwork.Application -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Application/bin/Debug/net10.0/Cluckwork.Application.dll
  Cluckwork.Infrastructure -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/src/Cluckwork.Infrastructure/bin/Debug/net10.0/Cluckwork.Infrastructure.dll
  Cluckwork.Application.Tests -> /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/tests/Cluckwork.Application.Tests/bin/Debug/net10.0/Cluckwork.Application.Tests.dll
Test run for /home/mforce/dev/cluckwork/.claude/worktrees/agent-ad437278695c375f1/tests/Cluckwork.Application.Tests/bin/Debug/net10.0/Cluckwork.Application.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.2 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 20 ms - Cluckwork.Application.Tests.dll (net10.0)

@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: a07b751b-0104-4ddf-a80b-ca681e5aefe6


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mforce mforce added this to the Modular monolith milestone Sep 14, 2026
@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Local Codex review round (CodeRabbit was rate-limited; owner rule is to substitute a local codex exec review). Eight findings, all confirmed against the code and fixed in e34556d, each with a fixture test:

  1. Inherited interface members were invisible to GetMethods(); constructed base interfaces are now walked so IReader<IQueryable<Flock>> counts.
  2. Nested public interfaces were excluded by IsPublic; now scanned when the declaring chain is public.
  3. Indexer parameters and event handler types were unchecked; now walked.
  4. EnumerableQuery<T> (implements IQueryable) passed the exact-definition rule; the rule is now typeof(IQueryable).IsAssignableFrom.
  5. Named delegates hid their signature; walked through Invoke.
  6. Generic parameter constraints were unchecked; generic parameters are walked through their constraints.
  7. A self-expanding generic DTO (Node<T> with a Node<List<T>> property) never terminated; property recursion is once per generic definition.
  8. The decision record named SeamSurfaceRealAssemblyTests.cs, which did not exist; the gate now lives there.

Verification: Cluckwork.Application.Tests 312/312, build 0 warnings. Codex's output: 8 findings, no dismissals.

@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Local Codex review, round 2 on e34556d (CodeRabbit's re-trigger was rate-limited). Five findings, all confirmed and fixed in 78f16d4, each with a fixture test:

  1. A member inherited from a base interface outside the scanned prefix (IReader<T> { IQueryable<T> Read(); } in Domain, IFlockReader : IReader<Flock> in Application) was never walked; base interfaces are now walked on their constructed form, so Read() reports IQueryable<Flock>.
  2. An interface's own generic constraints (IContract<T> where T : IQueryable<Flock>) were skipped; now walked.
  3. Skipping every special-name method also skipped static abstract operators; only property and event accessors are skipped now.
  4. Function pointers (delegate*<IQueryable<Flock>, void>) were opaque; parameter and return types are walked. The test project now allows unsafe blocks for that fixture.
  5. The decision record claimed coverage the walk did not have; it now lists inherited members, operators, indexers, events, constraints, delegates and function pointers, which is what the code does after 1 to 4.

Verification: Cluckwork.Application.Tests 316/316, 25 in SeamSurface*, build 0 warnings.

@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Local Codex review, round 3 on 78f16d4. One finding, rated exotic by the reviewer: a method-level generic constraint was skipped when the type parameter appeared nowhere else in the signature (void Register<T>() where T : IQueryable<Flock>). Fixed in c2ae9e7 with a fixture; a method's generic parameters are now walked through their constraints with one visited set per method.

Stopping the local review loop here, deliberately. Round 1 found 8 confirmed holes, round 2 found 5, round 3 found 1 exotic shape. The next round would be polishing against C# shapes this codebase does not use, which AGENTS.md's "prefer the boring guard" argues against. CodeRabbit's own round still lands when its window resets, and its findings will be handled.

Verification: Cluckwork.Application.Tests 317/317, build 0 warnings.

@mforce

mforce commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Codex (gpt-5.6-sol) review of record on c2ae9e7, in lieu of the CodeRabbit bot per the owner's decision: every fix claimed in the earlier rounds is present, no new defect. VERDICT: approve, 0 findings. Report kept at /tmp/514/codex-sol-875.md on the build machine.

Ready to merge from the reviewer's side: CI green on c2ae9e7, closes #847, on the Modular monolith milestone.

)

Epic #514 slice 5, Track B. Reflection over every public interface under
Cluckwork.Application.Features.* and Cluckwork.Application.Common, recursing
through parameters, return types, generic arguments, arrays, byref and the
public properties of any Cluckwork.* type reached. Forbidden: DbContext,
DbSet<>, IQueryable, any Microsoft.EntityFrameworkCore type, the bases
Entity<> and AggregateRoot<> as a declared type, any Cluckwork.Infrastructure
type. A second leg pins that the Application assembly references no EF, Npgsql
or Infrastructure assembly. 35 interfaces inspected today, floor 30, zero
violations.

Mutations recorded red, then reverted: IQueryable<Flock> on IFlockRepository;
the EF package plus a DbSet<Expense> return (both legs red); the package
without a lock regeneration (NU1004); a Task<Money> return stays green.
…legate holes (#847)

Local Codex review round while CodeRabbit was rate-limited. Eight findings,
all confirmed and fixed: constructed base interfaces are walked so an
inherited generic argument counts; nested public interfaces are scanned;
indexer parameters and event handler types are checked; any type
implementing IQueryable is forbidden, not only the exact generic
definitions; named delegates are walked through Invoke; generic parameters
are walked through their constraints; property recursion happens once per
generic definition so a self-expanding DTO terminates; the real-assembly gate
moves to the file the decision record names. One fixture test per hole.
…on pointers (#847)

Codex round 2 on the seam-surface walk. Members inherited from a base
interface are checked on the constructed base so substituted arguments count;
an interface's own generic constraints are walked; only property and event
accessors are skipped, so static abstract operators are checked; function
pointer signatures are walked. The test project allows unsafe blocks for the
function-pointer fixture.
@mforce
mforce force-pushed the chore/847-seam-surface branch from c2ae9e7 to c44b4cb Compare September 15, 2026 02:41
@mforce

mforce commented Sep 15, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main after #872 merged (c2ae9e7 → c44b4cb). Two add/add conflicts, both resolved by keeping both sides: the #842 and #847 bullets in AGENTS.md, and both rows in docs/decisions/README.md. No code changes; Architecture and Documentation guards pass locally on the rebased head (84/84).

@mforce
mforce merged commit 9f7ec36 into main Sep 15, 2026
16 checks passed
@mforce
mforce deleted the chore/847-seam-surface branch September 15, 2026 02:56
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.

[B] #514 slice 5: seam-surface guard — no persistence type crosses a seam

1 participant