Skip to content

refactor(flocks): put flock lifecycle behind an IFlockModule contract - #1021

Merged
mforce merged 11 commits into
mainfrom
refactor/852-flock-contract
Oct 2, 2026
Merged

mforce merged 11 commits into
mainfrom
refactor/852-flock-contract

Conversation

@mforce

@mforce mforce commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Closes #852

Epic #514, Track C slice 10. This puts Flock Management behind a contract, the third module after Finance (#849) and Farm (#851). Nothing a user sees changes. The only behaviour change is in the simulation seeder, described below.

What changed

  • IFlockModule is the adapter surface. It carries the six lifecycle commands, the picker search, birds-removed counts and the movement ledger page. FlockEndpoints, DemoDataSeeder and SimulationDataSeeder call it.
  • IFlockLookup is the read port. It provides GetAsync, GetForFlockScopedWriteAsync, GetDisplayNamesAsync and ResolveByNameAsync, and returns a FlockDetails copy that carries CanRecordProductionOn(date). The other endpoints and every peer handler call it.
  • IMortalityLedger is the write port. AppendAsync adds one daily-entry movement to the caller's unit of work and never saves. A positive count records Mortality and a negative count records an Adjustment, which is the rule Submit, Adjust and Void already used. It also takes over the note truncation from AdjustDailyEntryHandler.MovementNote, which is deleted.
  • Every peer caller converts:
    • Access: AssignFlockHandler.
    • Finance: Create Expense and Adjust Expense.
    • General Inventory: Record Feed Usage, Record Water Usage and Update Water Usage.
    • Egg Operations: Record, Submit, Adjust and Void daily entry.
  • Each port method calls the same repository method its caller used before. SQL, tracking, the Add discovery guard for flock-scoped EF query filters #613 filters, the FlockScopeGuard's fail-open branch grants account-wide flock access to an unresolved actor #787 guard order and [C] #514 slice 13: General Inventory contract and the flock-eligibility read #855's feed-usage order (item lock, then eligibility, then lots, with the flock row unlocked) do not change.
  • Flock.CanRecordProductionOn gains a static overload over status and depletion date, so the aggregate and the copy share one rule.
  • SeamSurfaceScanner.ScanContracts now also walks every public type that derives from a walked class. Without that, a Flock property added to FlockNameResolution.NotFound passed the contract walk. I checked this by mutation.
  • docs/decisions/852-flock-contract.md is new. AGENTS.md gains one [C] #514 slice 10: Flock Management contract, eligibility and mortality ports #852 paragraph, and the [C] #514 slice 7: Finance pilot — first module behind a contract #849 record now reflects the change.

Checkpoint decisions (owner-approved)

  1. Three interfaces, not one. FlockModule builds six handlers on every resolve. Narrow ports keep a peer request from constructing them, which is the reason [C] #514 slice 9: Farm contract, with tenant identity staying in Platform #851 left FarmClock alone. The decision record says when later slices should split the same way.
  2. Every peer caller converts in this slice, Egg Operations included. [C] #514 slice 11: Egg Operations contract and the opaque stock seam #853 and [C] #514 slice 13: General Inventory contract and the flock-eligibility read #855 inherit those callers already converted.
  3. The peer-module guard is deferred. The decision record names the gap: a peer that injects IFlockRepository again stays green.
  4. Seeder behaviour change. SimulationDataSeeder.EnsureFlockAsync now resolves by name. On Ambiguous it fails the seed and names every candidate id, where it used to take the first match. It is the method's only caller.
  5. Flock Management to Egg Operations is a schema-only foreign key (FK_BirdMovements_DailyEntries_DailyEntryId, from a bare Guid?). The decision record says [C] #514 slice 17: assembly split — conditional on Track A/B evidence #859 has to break it.

Ledger and reach

module-ledger.json
  owners.FlockManagement.contract        = [IFlockModule, IFlockLookup, IMortalityLedger, FlockDetails,
                                            FlockReference, FlockNameResolution, BirdMovementDetails,
                                            FlockEligibility, FlockStatus, BirdMovementType,
                                            CreateFlockCommand, UpdateFlockCommand, RecordBirdMovementCommand]
  owners.FlockManagement.implementations = [BirdMovementRepository, FlockRepository]
  compatibilityExceptions  + 6 rows (#850):
      DemoDataSeeder.CleanupPartialSeedAsync, .SeedAsync, .SeedDemoAsync       -> #858
      SimulationDataSeeder.ComputeCountsAsync, .SeedExplicitBirdMovementsAsync  -> #858
      UserRoleAssignmentRepository.ListByNameByUserAsync                        -> #857
  edges  Access/EggOperations/Finance/GeneralInventory -> FlockManagement: reasons narrowed
         to the ports; symbols, kinds and edge set unchanged
coupling-matrix.md   unchanged (Platform/adapters -> FlockManagement stays A (23))
Measure main this PR
Adapter contract bypasses into Flock Management (placeholder contract on 4c429f0) 34 crossings, 15 types, 23 rows 0
Adapter rows reaching Flock Management (owner level, #846) 23 23
Peer injections of IFlockRepository / IBirdMovementRepository 13 in 10 handlers 0
#850 compatibility exceptions 12 18

SimulationDataSeeder.EnsureFlockAsync no longer reads db.Flocks directly, so the seven reads my pre-merge probe predicted are now six.

Change map

BEFORE                                         AFTER

FlockEndpoints (10 routes/helpers)             FlockEndpoints
 ├─► 6 lifecycle handlers                       ├─► IFlockModule ◄── new front door
 ├─► IFlockRepository (Flock entity)            └─► IFlockLookup (GetFlock 404 check)
 └─► IBirdMovementRepository                           │
                                                       ▼
                                               FlockModule (forwards)
                                                ├─► 6 handlers (unchanged)
                                                ├─► IFlockRepository
                                                └─► IBirdMovementRepository

DailyEntry/Expense/FeedUsage/Water/Report      same endpoints
endpoints                                       └─► IFlockLookup (display names, 404)
 └─► IFlockRepository

DemoDataSeeder / SimulationDataSeeder          DemoDataSeeder / SimulationDataSeeder
 ├─► Create/Deplete/Archive/RecordMovement      ├─► IFlockModule
 ├─► IFlockRepository, FlockStatus              ├─► IFlockLookup.ResolveByNameAsync
 └─► db.Flocks.FirstOrDefault(name) ◄ 1st match │      Ambiguous ─► seed fails, names ids
                                                └─► db.Flocks / db.BirdMovements reads
                                                     stay (#850 rows, #858)

AssignFlockHandler (Access)                    AssignFlockHandler          ─► IFlockLookup
Create/AdjustExpenseHandler (Finance)          Create/AdjustExpenseHandler ─► IFlockLookup
Record/UpdateWater, RecordFeedUsage (Inv.)     same three                  ─► IFlockLookup
 └─► IFlockRepository (Flock entity)              (feed: lock item ─► eligibility ─► lots)

Record/Submit/Adjust/VoidDailyEntry            Record/Submit/Adjust/VoidDailyEntry
 ├─► IFlockRepository (eligibility)             ├─► IFlockLookup     (eligibility)
 └─► IBirdMovementRepository.AddAsync           └─► IMortalityLedger.AppendAsync
       (inside caller's unit of work)                 (adds to caller's unit of work,
                                                       never saves)

End state of files later slices also change:

Transaction atomicity evidence

MortalityLedgerAtomicityTests runs the handlers outside HTTP, where the idempotency middleware's request transaction does not wrap Submit's single SaveChangesAsync. It faults the audit write, which comes after the mortality append, and asserts that the movement count, entry status and Version are unchanged.

Mutation Submit test Void test
none green green
MortalityLedger calls SaveChangesAsync after AddAsync red green
same, plus ExecuteInTransactionAsync runs without a transaction red red

Void (and Adjust, which uses the same ExecuteInTransactionAsync path) stays atomic under an early save because its own transaction rolls the save back. The second mutation shows the Void test detects a partial commit once nothing holds that transaction. I reverted every mutation.

Other mutations (all red, all reverted)

  • Adding a Flock property to FlockNameResolution.NotFound turns ModuleContractRealAssemblyTests red. It was green before the scanner change.
  • Adding a Task<Flock?> default method to IFlockLookup turns ModuleContractRealAssemblyTests red.
  • Removing the derived-case walk turns SeamSurfaceTests.Contract_AggregateInADerivedCase_IsAViolation red.
  • Swapping DepletedOn and ArchivedOn in FlockLookup.ToDetails turns 2 FlockLookupTests red.
  • IgnoreQueryFilters() in FlockRepository.ListByNameAsync turns all 3 FlockNameResolutionTests red.
  • Ordering candidates by descending id turns the ambiguous-order test red.
  • Restoring db.Flocks.FirstOrDefaultAsync(name) in the seeder turns SimulationDuplicateFlockNameTests red. The re-run then fails later, at the count check.
  • Adding an IFlockRepository parameter to ReportEndpoints.Production turns AdapterReachRealTreeTests red with a contract bypass.

Test baselines (measured, not quoted)

Project main 6e3f584 this PR d0f1229
Domain 495 495
Application 639 654 (+15)
AppHost 10 10
Integration 1873 1879 (+6)

All green. dotnet build Cluckwork.sln reports 0 warnings and 0 errors. No existing test file was edited; SeamSurfaceTests.cs only gained a fixture and a theory.

Race, lifecycle, scope and rollback suites, run per class at the head, all passing:

Suite Tests
BirdMovementTests (incl. ParallelSubmits_ExactlyOneMortalityMovement) 5
SubmitDailyEntryTests 13
DailyEntryAdjustTests (incl. ParallelAdjustAndVoid_SameBaseVersion_ExactlyOneWins) 13
DailyEntryGradeTests 9
FlockManagementTests 8
FlockScopeTests 16
FlockScopeMiddlewareTests 13
FeedUsageTests 13
WaterUsageTests 7
ExpensesTests 5
SeedAndFlockTests 3
MortalityLedgerAtomicityTests (new) 2
FlockNameResolutionTests (new) 3
SimulationDuplicateFlockNameTests (new) 1

The ImagePin guards pass (2). No migration, entity configuration or AppDbContext file changed, so the EF model is identical.

Not in this PR

@mforce

mforce commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Review of record: Codex gpt-6-astra (xhigh), round 1, at head d0f1229f. Posted by the coordinator. Log paths named below are local to the review host.

Review of PR #1021

Reviewed head d0f1229f938801ef74092fcdda65c44ed7e60676 against base 6e3f58454a8de20320efe1e8c4a0bd68aac7c099, in the assigned detached checkout. Reviewed all 40 changed files and the requested decision records and issue audit. No delegation or GitHub writes.

Finding

P2 · CONFIRMED · Guard defect, not a product defect
tests/Cluckwork.Application.Tests/Architecture/SeamSurfaceScanner.cs:323

The new derived-type walk searches only the base type's assembly, so it misses an Application subclass of Domain's Result that exposes a domain aggregate through an existing module contract.

Concrete failure scenario: add this public type in Cluckwork.Application.Features.Flocks, then have FlockModule.DepleteAsync await its existing handler and return this wrapper around the handler result and loaded flock:

public sealed class ReviewFlockResult(Cluckwork.Domain.Flocks.Flock flock, Result result)
    : Result(result.IsSuccess, result.Error)
{
    public Cluckwork.Domain.Flocks.Flock Flock { get; } = flock;
}

IFlockModule.DepleteAsync still declares Task<Result>, but the returned object now exposes a tracked Flock. DerivedCases(Result) enumerates only Cluckwork.Domain; the wrapper lives in Cluckwork.Application. Both assemblies are already part of ModuleContractRealAssemblyTests' known contract universe.

Evidence: I made that mutation and ran ModuleContractRealAssemblyTests, AdapterReachRealTreeTests, and ModuleLedgerRealTreeTests. All 5 tests passed. Keeping the same mutation and temporarily adding Application's types to the derived-type search made the contract test fail with:

IFlockModule.DepleteAsync exposes Cluckwork.Domain.Flocks.Flock via
Task<Result> -> Result -> ReviewFlockResult -> ReviewFlockResult.Flock

Logs: /tmp/1021-mutation-cross-assembly-derived.log and /tmp/1021-mutation-cross-assembly-derived-expanded-scan.log. Both mutations were reverted.

The new walk correctly closes the same-assembly FlockNameResolution.NotFound hole, but its broader claim to walk every public derived type is incomplete. Search the known contract assemblies and add a cross-assembly fixture, or explicitly accept and document this narrower coverage. This is separate from the accepted peer-module injection gap. No existing production method in this PR returns the leaky wrapper.

Product correctness

No product defect found.

  • Submit still adds mortality after the egg lots and before audit, then uses its single existing SaveChangesAsync. Adjust and Void still append inside their existing ExecuteInTransactionAsync callbacks, before audit and the unit-of-work save. The port calls the same scoped IBirdMovementRepository.AddAsync and neither saves nor starts a transaction.
  • Entry reads, scope checks, client Version checks, lot locks, and failure branches retain their order. UnitOfWork, AmbientTransaction, SingleAttemptExecution, and BirdMovementRepository are byte-identical to the base. The Add transient-DB resilience (EnableRetryOnFailure) on the request path #269 retry boundary is unchanged.
  • All ten converted peer handlers retain their repository-query sequence through the lookup. Feed usage retains its preliminary flock read, followed by item lock, eligibility read, and lot locks. No flock lock was added.
  • Eligibility's switch body is byte-identical after substituting the depletedOn parameter for the DepletedOn property. All eleven declared FlockDetails fields are copied correctly, including both lifecycle dates and Version. The copy carries the fields consumed by the converted callers; it does not copy account ownership or database chronology metadata.
  • Endpoint routes, authorization, validation, response mapping and failure status mapping are unchanged. ListMovementsAsync preserves the flock-existence query before the movement query and preserves the null-to-404 result.
  • Name resolution uses exact equality across all statuses, retains tenant and flock-scope filters, and orders all candidates by PostgreSQL UUID order. The seeder's ambiguity refusal is the only product behavior change found.

Item 4: pre-existing feed-usage stale read

The lookup does not worsen this issue. Both calls still use the same tracked repository query. Creating a new FlockDetails object does not refresh the underlying EF entity.

I ran a temporary real-Postgres probe: read through the lookup, archive and commit in another scope, then read again in the original scope. The second lookup returned a different record object with stale Active status; the old repository method also returned Active. A fresh scope returned Archived. The probe passed and was removed. Its log is /tmp/1021-review-tracking-probe.log.

The port makes a later fix easier to contain: callers already consume immutable records, so a dedicated untracked read can replace the port's implementation without changing those callers. It must remain separate from the tracked repository reads used by lifecycle mutations. This pre-existing defect is not a finding against this PR.

Independent verification

Check Result
Full solution build after restoring mutations 0 warnings, 0 errors
Domain tests 495 passed
Application tests 654 passed
Selected real-Postgres integration tests 123 passed
Restored rollback tests plus temporary tracking probe 3 passed
Final contract, seam, lookup, mortality and matrix checks 54 passed
Matrix regeneration with CLUCKWORK_REGENERATE_MATRIX=1 No file change
Final git status --porcelain and git diff --exit-code Clean

The 123 integration tests comprise BirdMovement 5, SubmitDailyEntry 13, DailyEntryAdjust 13, DailyEntryGrade 9, FlockManagement 8, FlockScope 16, FlockScopeMiddleware 13, FeedUsage 13, WaterUsage 7, Expenses 5, SeedAndFlock 3, MortalityLedgerAtomicity 2, FlockNameResolution 3, SimulationDuplicateFlockName 1, RetryBoundary 11 and BootstrapRetryBoundary 1. This includes the parallel submit, parallel adjustment, adjust-versus-void, lifecycle, scope and rollback cases. I did not rerun the entire integration project. The PR's CI checks were green; image publishing was skipped as expected for the PR.

Mutation Observed result
Mortality port saves immediately after append Submit rollback test failed; Void passed
Same early save, with the unit-of-work transaction removed Both rollback tests failed
Inject IFlockRepository and RecordBirdMovementHandler into ReportEndpoints.Production Adapter guard rejected both bypasses
Return Task<Flock?> through a new lookup method Contract guard failed
Put IReadOnlyList<BirdMovement> in a nested record exposed by FlockDetails Contract guard failed
Put a Flock property on FlockNameResolution.NotFound Contract guard failed
Disable derived-type discovery Both new derived-case fixture cases failed
Return the Application Result subclass described above Five architecture checks stayed green
Include Application in discovery for that same mutation Contract guard failed

The Submit early-save failure observed (1, Submitted, 2) instead of (0, Draft, 1). With the transaction also removed, Void observed (2, Voided, 3) instead of (1, Submitted, 2). These are durable-state assertion failures, not build failures or unrelated exceptions. All mutations were reverted.

The ledger declares the three ports and their carried types, preserves the peer edge kinds and symbols, and lists the two repository implementations. The six #850 rows match their readers and tables: five Platform seeder rows ending at #858 and the Access assignment join ending at #857. The regenerated matrix is unchanged. The decision records accurately describe the transaction, tracking, seeder and schema-FK boundaries, apart from the derived-walk coverage qualification in the finding. The accepted peer-module guard gap is not reported as a defect. The clean solution build also passed the #985 style gate.

Nits

None.

Verdict: Request changes for one confirmed P2 contract-guard gap; no product defect found.

@mforce

mforce commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Codex Astra round 1 (at d0f1229f): response. Fixed in 99e6dbbc.

P2 (CONFIRMED). The derived-type walk searched only the base type's assembly (SeamSurfaceScanner.cs:323).

  • ScanContracts now takes knownAssemblies. It searches for derived cases in the contract types' own assemblies, the base type's assembly and the known assemblies. ModuleContractRealAssemblyTests passes its existing Application and Domain set. The universe is explicit, so it never depends on which assemblies happen to be loaded.
  • The universe travels in a small ScanRules record beside the rule table. That replaced the ReferenceEquals(rules, ContractRules) checks. [B] #514 slice 5: seam-surface guard — no persistence type crosses a seam #847's non-contract scan is unchanged.
  • New fixture SeamSurfaceTests.Contract_AggregateInADerivedCaseFromAnotherAssembly_IsAViolation. A test-assembly FlockCarryingResult : Result (Domain) carries a Flock, and a fixture contract returns Task<Result>.
  • The 852 and 849 decision records now name the cross-assembly search.

Mutation evidence. The reviewer's mutation is an Application ReviewFlockResult : Result carrying a Flock, returned from FlockModule.DepleteAsync while the interface still says Task<Result>. All mutations were reverted.

State Result
Reviewer's mutation, fixed scanner ModuleContractRealAssemblyTests red: ... exposes Cluckwork.Domain.Flocks.Flock via Task<Result> -> Result -> ReviewFlockResult -> ReviewFlockResult.Flock
Reviewer's mutation, base-assembly-only walk (the d0f1229f behaviour) ModuleContractRealAssemblyTests green, which reproduces the gap
No product mutation, base-assembly-only walk the new fixture test red
Restored Architecture 333/333 green; Application 655/655; solution build 0 warnings

Pre-existing feed-usage stale read. Not changed here, as agreed. It gets its own PR after this one merges.

@mforce

mforce commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Review of record: Codex gpt-6-astra (xhigh), round 2, at head 99e6dbbc. Posted by the coordinator. Log paths named below are local to the review host.

PR #1021, round-two review

Reviewed the five-file fix from d0f1229f938801ef74092fcdda65c44ed7e60676 to 99e6dbbcbc6608efab2c50e50d4e86525f455319 in the assigned checkout. Read the worker's response and reviewed the fix myself.

Original finding

Resolved. Repeating the original Application ReviewFlockResult : Result mutation now fails ModuleContractRealAssemblyTests with the expected path:

IFlockModule.DepleteAsync exposes Cluckwork.Domain.Flocks.Flock via
Task<Result> -> Result -> ReviewFlockResult -> ReviewFlockResult.Flock

This is a contract assertion failure, not a build failure. A public nested generic subclass with an explicitly typed Flock property is also rejected. The new fixture and the expanded assembly search work for the original case.

Findings

1. P2 · CONFIRMED · Generic result erasure still bypasses the guard

tests/Cluckwork.Application.Tests/Architecture/SeamSurfaceScanner.cs:336

The derived-type enumeration obtains generic definitions rather than the closed types actually returned, allowing Result<Flock> to cross an existing Task<Result> contract undetected.

Concrete failure scenario: replace only FlockModule.DepleteAsync with:

public async Task<Result> DepleteAsync(Guid id, CancellationToken ct)
{
    var result = await depleteFlock.HandleAsync(id, ct);
    var flock = await flocks.GetByIdAsync(id, ct);
    return result.IsFailure || flock is null ? result : Result.Success(flock);
}

This uses the existing Result<T> : Result; it introduces no new assembly, subtype declaration or contract signature. GetTypes() finds Result<T>, whose unconstrained T reaches the early return at scanner line 233. It never finds the returned construction Result<Flock>.

Evidence: the contract, adapter and module-ledger checks all passed, 5/5. A temporary real-Postgres integration test resolved IFlockModule through normal DI, created and depleted a flock, and retrieved the actual depleted Flock from the returned object's public property. That test passed, 1/1. The leak is valid C# and reachable through the real module, not merely a hypothetical type shape.

Logs: /tmp/1021-r2-generic-erasure-guards.log and /tmp/1021-r2-generic-erasure-runtime.log.

This is a guard defect, not a product defect in the unmodified PR. Prevent entity-bearing generic results from being erased to the non-generic contract, or explicitly accept and document this runtime-type limitation. Enumerating more assemblies does not solve it.

2. P2 · CONFIRMED · The real scan omits an assembly that can supply contract results

tests/Cluckwork.Application.Tests/Architecture/ModuleContractRealAssemblyTests.cs:23

Passing only Application and Domain still permits a DI-supplied Infrastructure subclass of Result to carry a Flock through IFlockModule without a contract violation.

Concrete failure scenario, reproduced without making Application reference Infrastructure:

  • Add an Application IReviewResultWrapper with Result Wrap(Result result, Flock flock) and inject it into FlockModule.
  • Implement it in Infrastructure, returning a public ReviewFlockResult : Result with a public Flock property.
  • Register that implementation in the existing API composition root and have DepleteAsync return its wrapper after invoking the original handler.

The contract still returns Task<Result>. The concrete object comes from Infrastructure through an ordinary Application interface. The scanner never visits it because Infrastructure is absent from knownAssemblies and is neither the contract's nor the base class's assembly.

Evidence: contract, adapter, module-ledger and #847 real-assembly checks passed, 7/7. A temporary real-Postgres test resolved the module from DI, depleted a flock, asserted that the result's assembly was Cluckwork.Infrastructure, and retrieved the matching depleted Flock payload. It passed, 1/1. Keeping that mutation and adding Infrastructure to knownAssemblies made the contract test fail correctly:

IFlockModule.DepleteAsync exposes
Cluckwork.Infrastructure.Repositories.ReviewFlockResult via
Task<Result> -> Result -> ReviewFlockResult

Logs: /tmp/1021-r2-infrastructure-guards.log, /tmp/1021-r2-infrastructure-runtime.log, and /tmp/1021-r2-infrastructure-expanded-universe.log.

This is a guard defect, not a product defect in the unmodified PR. Include the production assemblies that can supply contract results, enforce an implementation boundary that excludes them, or explicitly accept this reachable gap. The revised decision record names the assembly list, but does not state the resulting unenforced case. This does not involve the accepted peer-module repository-injection gap.

Product correctness and scope

No product defect found. The fix changes tests and documentation only. The two findings are remaining guard limitations exposed by the requested second-round attacks; the original cross-assembly case is fixed. Neither mutation is present in the delivered product code.

The #847 scan still receives no contract-case assembly set, so its IsContract branch remains false. Its real-assembly checks and the full Application suite passed. No false positive or performance blow-up appeared on the real tree. The contract test took about 21 ms in this round's baseline, versus 31 ms in the saved round-one baseline; these are individual observations, not a benchmark.

Item 4: pre-existing feed-usage stale read

Unchanged and outside this fix-only review. No production lookup, repository or feed-usage code changed. It remains excluded from findings against this PR.

Independent verification

Check or mutation Result
Unmodified Application suite 655 passed
Original Application ReviewFlockResult mutation Contract test failed with the expected aggregate path
Public nested Carry<T> : Result with explicit Flock property Contract test failed with Carry<T>.Flock
Existing Result<Flock> returned as Result 5 architecture checks passed; runtime leak probe passed
Infrastructure result subclass supplied through DI 7 architecture checks passed; runtime leak probe passed
Same Infrastructure mutation, with Infrastructure included in the scan Contract test failed with the Infrastructure type
Full solution build after restoration 0 warnings, 0 errors
Final architecture suite after restoration 333 passed
Final diff and checkout status Clean at 99e6dbbc

Every mutation and temporary runtime test was reverted. No commits, pushes or GitHub comments were made.

Nits

None.

Verdict: Original P2 resolved; request changes or explicit acceptance for two confirmed remaining P2 guard gaps, with no product defect found.

@mforce

mforce commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Codex Astra round 2 (at 99e6dbbc): response. Both items are addressed in fe067c99.

1. A closed generic Result<Flock> returned through Task<Result> (SeamSurfaceScanner.cs:336). Documented, not fixed, as the owner decided. The 852 decision record ("What this does NOT cover") and the 849 contract record now say the contract walk checks declared and statically discoverable types only. A generic constructed at runtime, such as Result<Flock> returned through a non-generic Result signature, never appears in a declaration the walk can enumerate, so only review catches it.

2. An Infrastructure subclass of Result supplied through DI (ModuleContractRealAssemblyTests.cs:23). Fixed. The real contract test now passes Application, Domain and Infrastructure as knownAssemblies. Api is left out because Cluckwork.Application.Tests does not reference it, and the decision record names that limit.

Mutation evidence. The reviewer's mutation is a public Cluckwork.Infrastructure.Repositories.ReviewFlockResult : Result with a public Flock property. It was reverted.

State ModuleContractRealAssemblyTests
Real tree, fixed test green
Mutation, fixed test red: IFarmModule.UpdateSettingsAsync exposes Cluckwork.Infrastructure.Repositories.ReviewFlockResult via Task<Result> -> Result -> ReviewFlockResult (also every other Task<Result> member)
Mutation, the 99e6dbbc test (no Infrastructure) green, which reproduces the gap
Restored Architecture 333/333 green

The review loop was stopped deliberately by the owner after two consecutive rounds that found no product defect (rounds 1 and 2 found only guard gaps). No round 3 will be triggered.

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.

[C] #514 slice 10: Flock Management contract, eligibility and mortality ports

1 participant