Skip to content

Reorganize Imperium into per-bounded-context namespaces with qualified facades#136

Merged
bartul merged 11 commits into
masterfrom
refactor/reorganize-imperium-namespaces
May 22, 2026
Merged

Reorganize Imperium into per-bounded-context namespaces with qualified facades#136
bartul merged 11 commits into
masterfrom
refactor/reorganize-imperium-namespaces

Conversation

@bartul

@bartul bartul commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

Restructure src/Imperium from a flat file list under namespace Imperium into one folder per bounded context, each owning its own namespace plus a [<RequireQualifiedAccess>] facade module. Follows the recommended approach (Approach A) in /tmp/research-topic.md. No behaviour or public-call-semantics change beyond the explicit Rondel.execute / Accounting.execute qualification.

Folder layout (compile order mirrors Imperium.fsproj):

  • Primitives/Primitives.fs, AsyncExtensions.fs
  • Contract/Contract.fs, Accounting.fs, Rondel.fs (namespace Imperium.Contract)
  • Gameplay/Gameplay.fsi/.fs (namespace Imperium.Gameplay, [<RQA>] module Gameplay)
  • Accounting/Commands / Events / Dependencies / Handlers / Accounting facade (namespace Imperium.Accounting, [<RQA>] module Accounting)
  • Rondel/Types / Commands / Events / State / Dependencies / Movement / Invoices / Handlers / Queries / Rondel facade (namespace Imperium.Rondel, [<RQA>] module Rondel)

Caller convention: open Imperium.Rondel; Rondel.execute deps cmd (bare execute is no longer in scope; same for Accounting).

Notable design decisions:

  • Type-companion modules (MoveCommand.fromContract, AccountingEvent.toContract, etc.) live in the same .fsi/.fs pair as the type they accompany — F# disallows splitting them across files of the same assembly.
  • Cross-file implementation helpers that need to stay hidden from external consumers (Space.toString, Space.fromString, Space.distance, Action.toString, RondelBillingId.create, RondelBillingId.newId) are declared as val internal in the relevant .fsi.
  • Domain type names keep their BC-qualified prefixes (RondelCommand, AccountingDependencies, etc.) so terminal/web composition roots can open multiple bounded contexts without ambiguity.
  • Each bounded context isolates its concerns into dedicated files: message types (Commands, Events), state (State), dependency contracts (Dependencies), and the public facade.

Commits (8):

  • 497c325 Move Imperium source files into bounded-context folders
  • f5dae47 Migrate Accounting to its own namespace with qualified facade module
  • 96769e0 Migrate Rondel to its own namespace with per-concept file split
  • 0cf0f21 Migrate Gameplay to its own namespace with qualified facade module
  • f5421b7 Document new namespace + facade layout in AGENTS.md
  • 7d71613 Split domain dependency types into their own Dependencies file pair
  • 87429d0 Rephrase type-companion module placement as a positive rule
  • eaf5803 Split Commands and Events into dedicated file pairs per BC

Test plan

  • dotnet build Imperium.slnx — 0 warnings, 0 errors after every step
  • dotnet test — 130 / 130 passing after every step
  • dotnet fantomas --check . — clean after every step
  • dotnet run --no-build --project tests/Imperium.UnitTests/Imperium.UnitTests.fsproj -- --render-spec-markdown — spec markdown still renders
  • CI on PR green

Summary by CodeRabbit

  • Documentation

    • Updated architectural guidelines and module organization patterns.
  • Refactor

    • Reorganized internal module structure for improved maintainability and code organization.

Review Change Stack

bartul added 8 commits May 22, 2026 21:22
Reorganize src/Imperium flat layout into per-context folders
(Primitives, Contract, Gameplay, Accounting, Rondel) without
namespace or module changes. Imperium.fsproj compile order is
updated to match the new paths.

Preparation step for migrating each bounded context onto its own
namespace + qualified facade module per the recommended approach
in /tmp/research-topic.md.
- Replace 'namespace Imperium; module Accounting' with
  'namespace Imperium.Accounting' across the bounded context
- Split Accounting.fsi/.fs into Types.fsi/.fs (commands, events,
  dependencies, contract transformations) and Handlers.fs
  (internal command handlers), keeping the type+companion-module
  pairs together in the same file (FS0250 disallows splitting
  them across files within an assembly)
- Add a [<RequireQualifiedAccess>] module Accounting facade
  exposing only val execute as the public router
- Update callers (Imperium.Terminal.Accounting.Host and the
  Accounting/RondelHost test modules) to call Accounting.execute
  and to open Imperium.Accounting rather than abbreviating the
  former module

First bounded context migration toward the recommended layout in
/tmp/research-topic.md. Rondel and Gameplay follow.
- Replace 'namespace Imperium; module Rondel' with
  'namespace Imperium.Rondel' across the bounded context
- Split the 816-line Rondel.fs/.fsi into per-concept files:
    Types         value types, commands, events, outbound/inbound
                  routing DUs, query types/views, and the
                  type-companion transformation modules that must
                  live next to their types (FS0250)
    State         RondelState, PendingMovement, their companions,
                  plus dependency types (LoadRondelState,
                  RondelEffects, CommitRondelEffects, etc.)
    Movement      internal pure Move decision pipeline (renamed
                  from internal module Move to module Movement to
                  align with the recommended layout)
    Invoices      internal pure OnInvoicePaid / OnInvoicePaymentFailed
    Handlers      internal load -> pure execute -> effects glue
                  plus the SetToStartingPositions pure module and
                  a private toEffects tuple lifter
    Queries       internal getNationPositions / getRondelOverview
- Add a [<RequireQualifiedAccess>] module Rondel facade exposing
  only val execute, handle, getNationPositions, getRondelOverview
- Expose cross-file helper functions (Space.toString, fromString,
  distance and RondelBillingId.create, newId) as val internal in
  the .fsi files; the .fsi controls assembly-internal visibility
  once a signature exists for a file
- Update callers (Imperium.Terminal.Rondel.Host and the Rondel
  test module) to call Rondel.execute / Rondel.handle /
  Rondel.getNationPositions / Rondel.getRondelOverview

Second bounded context migration toward the recommended layout
in /tmp/research-topic.md. Gameplay follows.
Replace 'namespace Imperium; module Gameplay' with
'namespace Imperium.Gameplay' and wrap the placeholder module in
[<RequireQualifiedAccess>] module Gameplay. The placeholder, the
internal GameId struct, and the internal NationId DU remain
inside the facade module since they are not yet exposed.

GameplayTests already opened Imperium.Gameplay; the change from
module-open to namespace-open is transparent because the test
file references no Gameplay members yet.

Third (and final) bounded context migration toward the
recommended layout in /tmp/research-topic.md.
Update the Project Structure and Rondel Implementation Patterns
sections to describe:

- The folder-per-bounded-context layout under src/Imperium/
  (Primitives, Contract, Gameplay, Accounting, Rondel) with the
  per-context compile order
- The namespace + facade convention: each BC lives in
  namespace Imperium.<BC> and exposes routers/queries only via
  a same-named [<RequireQualifiedAccess>] module facade, called
  as 'open Imperium.Rondel; Rondel.execute deps cmd'
- The FS0250 constraint that forces type+companion-module pairs
  to live in the same .fsi/.fs pair (folded into Types/State
  rather than a standalone Transformations file)
- The .fsi visibility rule: anything not in the signature file
  is private to its implementation file once a .fsi exists, so
  cross-file helpers (Space.toString/fromString/distance,
  RondelBillingId.create/newId) are exposed as val internal
- The Rondel file split into Types, State, Movement, Invoices,
  Handlers, Queries, Rondel and the rename of the pure module
  Move -> Movement
- Per-file locations for state transformations and handler glue
  (RondelState in State.fs, Movement in Movement.fs, etc.)
Both Accounting and Rondel previously co-located their dependency
type aliases (handler input/output contracts) with the rest of
the domain types or with state. Lift them into a sibling
Dependencies.fsi/.fs pair per bounded context so each file owns
one concern:

- Accounting/Dependencies.fsi/.fs holds PublishAccountingEvent
  and AccountingDependencies (compiled after Accounting/Types)
- Rondel/Dependencies.fsi/.fs holds LoadRondelState,
  RondelDependencies, RondelEffects, CommitRondelEffects,
  LoadRondelStateForQuery, RondelQueryDependencies (compiled
  after Rondel/State)

State.fsi/.fs and Types.fsi/.fs shrink accordingly; no behaviour
or public-API change.
Drop the FS0250 error code reference from AGENTS.md and state
the rule directly: a module that shares a type's name must live
in the same .fsi/.fs pair as the type. The compiler diagnostic
was incidental implementation trivia; the architectural rule is
what future contributors actually need.
Each bounded context now isolates its message types into their
own files instead of grouping them under Types:

Rondel/
  Commands.fsi/.fs  RondelCommand + SetToStartingPositionsCommand
                    + MoveCommand with their fromContract modules,
                    plus the outbound RondelOutboundCommand +
                    ChargeMovement/VoidCharge variants with their
                    toContract modules
  Events.fsi/.fs    RondelEvent + PositionedAtStart/ActionDetermined/
                    MoveToActionSpaceRejected with RondelEvent.toContract,
                    plus the inbound RondelInboundEvent +
                    InvoicePaid/InvoicePaymentFailed with their
                    fromContract modules
  Types.fsi/.fs     trimmed to value types (RondelBillingId, Action,
                    Space) and query types/views only

Accounting/
  Commands.fsi/.fs  AccountingCommand + ChargeNationForRondelMovement
                    + VoidRondelCharge with their fromContract modules
  Events.fsi/.fs    AccountingEvent + RondelInvoicePaid/PaymentFailed
                    with AccountingEvent.toContract
  Types.fsi/.fs     deleted (no value types left after the move)

Compile order: Types -> Commands -> Events -> State -> Dependencies
-> Movement/Invoices/Handlers/Queries -> Rondel for Rondel;
Commands -> Events -> Dependencies -> Handlers -> Accounting for
Accounting. Action.toString is now exposed as val internal in
Rondel/Types.fsi so RondelEvent.toContract can reach it from
Events.fs.
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@bartul has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 38 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6199db93-f9a4-4e9a-b8be-686d605da741

📥 Commits

Reviewing files that changed from the base of the PR and between eaf5803 and 33a06a1.

📒 Files selected for processing (7)
  • AGENTS.md
  • src/Imperium/Accounting/Accounting.fsi
  • src/Imperium/Imperium.fsproj
  • src/Imperium/Rondel/Queries.fs
  • src/Imperium/Rondel/Queries.fsi
  • src/Imperium/Rondel/Types.fs
  • src/Imperium/Rondel/Types.fsi
📝 Walkthrough

Walkthrough

This PR restructures the Accounting and Rondel bounded contexts from monolithic top-level modules into organized folder hierarchies with separated concerns: domain command/event contracts, dependencies, internal handlers, and public facades using qualified access and effect-based routing. The project file is updated accordingly, and all hosts and tests are requalified to use the new module names.

Changes

Bounded-Context Modularization

Layer / File(s) Summary
Documentation and Pattern Foundation
AGENTS.md, src/Imperium/Gameplay/Gameplay.fs, src/Imperium/Gameplay/Gameplay.fsi
AGENTS.md is rewritten to document the new bounded-context folder layout, [<RequireQualifiedAccess>] facade convention, namespace organization, and handler patterns with Load/Commit boundaries. Gameplay module is updated to demonstrate the pattern with namespace change and RequireQualifiedAccess attribute.
Accounting Bounded-Context
src/Imperium/Accounting/Commands.fs, src/Imperium/Accounting/Commands.fsi, src/Imperium/Accounting/Events.fs, src/Imperium/Accounting/Events.fsi, src/Imperium/Accounting/Dependencies.fs, src/Imperium/Accounting/Dependencies.fsi, src/Imperium/Accounting/Handlers.fs, src/Imperium/Accounting/Accounting.fs, src/Imperium/Accounting/Accounting.fsi
Accounting is modularized from a monolithic src/Imperium/Accounting.fs into a bounded-context folder with Commands (chargeNationForRondelMovement, voidRondelCharge) and fromContract transformations, Events (RondelInvoicePaid, RondelInvoicePaymentFailed) with toContract mapping, Dependencies (PublishAccountingEvent, AccountingDependencies), internal Handlers, and a public Accounting facade with execute router.
Rondel Domain Types and Persistence
src/Imperium/Rondel/Types.fs, src/Imperium/Rondel/Types.fsi, src/Imperium/Rondel/State.fs, src/Imperium/Rondel/State.fsi
RondelBillingId (value type), Action and Space enums with helper functions (distance, toString, fromString, toAction), and query DTOs (GetNationPositionsQuery, RondelPositionsView, RondelView) are defined. RondelState and PendingMovement provide constructors, mutators (withNationPosition, withPendingMove, withoutPendingMove), and contract serialization with toContract/fromContract and Result-based validation.
Rondel Command and Event Contracts
src/Imperium/Rondel/Commands.fs, src/Imperium/Rondel/Commands.fsi, src/Imperium/Rondel/Events.fs, src/Imperium/Rondel/Events.fsi, src/Imperium/Rondel/Dependencies.fs, src/Imperium/Rondel/Dependencies.fsi
Inbound commands (SetToStartingPositions, Move) and outbound commands (ChargeMovement, VoidCharge) with fromContract/toContract are defined. Outgoing events (PositionedAtStart, ActionDetermined, MoveToActionSpaceRejected) and inbound events (InvoicePaid, InvoicePaymentFailed) with contract mapping and Result validation are added. Dependencies define LoadRondelState/CommitRondelEffects for write-side and LoadRondelStateForQuery for read-side.
Rondel Handler Logic
src/Imperium/Rondel/Handlers.fs, src/Imperium/Rondel/Movement.fs, src/Imperium/Rondel/Invoices.fs
SetToStartingPositions.execute initializes state when absent; Movement.execute implements pure move decisions (rejected/free/paid with superseding); OnInvoicePaid and OnInvoicePaymentFailed handle inbound invoice events with pure state/event/command returns. Handlers module wraps pure operations via toEffects and async pipelines that load state, invoke logic, and return RondelEffects.
Rondel Public API and Queries
src/Imperium/Rondel/Rondel.fs, src/Imperium/Rondel/Rondel.fsi, src/Imperium/Rondel/Queries.fs
execute routes commands to handlers with effect commit; handle routes inbound events similarly. getNationPositions and getRondelOverview delegate to internal Queries and return optional view results. All public operations are exposed through a qualified Rondel facade module.
Integration and Project Wiring
src/Imperium/Imperium.fsproj, src/Imperium.Terminal/Accounting/Host.fs, src/Imperium.Terminal/Rondel/Host.fs, tests/Imperium.UnitTests/Accounting.fs, tests/Imperium.UnitTests/Rondel.fs, tests/Imperium.UnitTests/RondelHostTests.fs
Project file is updated with new modularized file structure. Accounting and Rondel hosts call Accounting.execute and Rondel.execute/handle with explicit qualification. Tests update qualified module names and use open Imperium.Accounting for dequalified type references.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • bartul/imperium#134: Both PRs implement the RondelEffects/CommitRondelEffects effect-based architecture with unified RondelDependencies and Load/Commit boundaries that are used throughout the new handler pipeline.
  • bartul/imperium#26: The PR continues the Rondel router refactor by exposing execute and handle entry points in a qualified Rondel facade, consistent with the retrieved PR's "execute/handle routers" API pattern.
  • bartul/imperium#7: The main PR's new RondelState and PendingMovement with toContract/fromContract persistence mapping builds on the same rondel persistence-to-domain-state refactor introduced in PR #7.

Poem

A rabbit hops through bounded-context halls,
Accounting cracks and Rondel sprawls—
Commands, events, clean facades shine,
From monoliths to modules so fine! 🐰✨
With Load and Commit now side by side,
The effects pipeline's our trusty guide.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: restructuring Imperium into per-bounded-context namespaces with [] facade modules, which is the core purpose of this refactoring.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/reorganize-imperium-namespaces

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/Imperium/Accounting/Events.fs (1)

6-22: ⚡ Quick win

Add standard section dividers and XML docs in the implementation file.

The .fsi is structured/documented, but this .fs file currently omits that pattern; keeping both aligned improves maintainability.

Suggested direction
+// ──────────────────────────────────────────────────────────────────────────
+// Events
+// ──────────────────────────────────────────────────────────────────────────
+
+/// Integration events published by Accounting to notify other bounded contexts.
 type AccountingEvent =
     | RondelInvoicePaid of RondelInvoicePaidEvent
     | RondelInvoicePaymentFailed of RondelInvoicePaymentFailedEvent
...
+/// Transform Domain event to Contract event for cross-boundary communication.
 module AccountingEvent =

As per coding guidelines: **/{Rondel,Accounting}/**/*.{fs,fsi} domain modules should follow a consistent sectioned structure with visual dividers and XML doc comments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Imperium/Accounting/Events.fs` around lines 6 - 22, Add the same section
dividers and XML documentation present in the .fsi to this implementation:
annotate the AccountingEvent union and the related types
(RondelInvoicePaidEvent, RondelInvoicePaymentFailedEvent) with XML doc comments
and split the module into the standard visual sections (e.g., header, types,
module functions) so the implementation mirrors the .fsi structure; ensure the
toContract function has a brief XML doc and is placed under the expected
"implementation" or "converters" divider to match coding guidelines.
src/Imperium/Accounting/Commands.fs (1)

7-33: ⚡ Quick win

Align this .fs file with the domain section/doc structure used in .fsi.

Please add the standard visual section dividers and XML docs in the implementation file as well, so the .fs/.fsi pair stays consistent and easier to scan.

Suggested direction
+// ──────────────────────────────────────────────────────────────────────────
+// Commands
+// ──────────────────────────────────────────────────────────────────────────
+
+/// Union of all accounting commands for routing and dispatch.
 type AccountingCommand =
     | ChargeNationForRondelMovement of ChargeNationForRondelMovementCommand
     | VoidRondelCharge of VoidRondelChargeCommand
...
+// ──────────────────────────────────────────────────────────────────────────
+// Transformations
+// ──────────────────────────────────────────────────────────────────────────

As per coding guidelines: **/{Rondel,Accounting}/**/*.{fs,fsi} domain modules should follow a consistent sectioned structure with visual dividers and XML doc comments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Imperium/Accounting/Commands.fs` around lines 7 - 33, Add the same visual
section dividers and XML documentation comments to this implementation file to
match the .fsi structure: annotate the top-level type AccountingCommand and the
record types ChargeNationForRondelMovementCommand and VoidRondelChargeCommand
with the corresponding XML docs, and add the same commented visual divider
blocks around the "Modules" area and each module implementation
(ChargeNationForRondelMovementCommand and VoidRondelChargeCommand) so the .fs
mirrors the .fsi domain sectioning and is easier to scan.
src/Imperium/Rondel/State.fs (1)

93-118: ⚡ Quick win

Refactor mutable accumulators in src/Imperium/Rondel/State.fs
The nationPositions and pendingMovements computations use let mutable with in-loop updates; rewriting them as Seq.fold keeps the code expression-based and consistent with the project style.

♻️ Suggested refactor
-        let nationPositions =
-            result {
-                let mutable positions = Map.empty
-
-                for nation, position in state.NationPositions |> Map.toSeq do
-                    match position with
-                    | None -> positions <- positions |> Map.add nation None
-                    | Some value ->
-                        let! space = Space.fromString value
-                        positions <- positions |> Map.add nation (Some space)
-
-                return positions
-            }
+        let nationPositions =
+            state.NationPositions
+            |> Map.toSeq
+            |> Seq.fold
+                (fun current (nation, position) ->
+                    result {
+                        let! positions = current
+                        match position with
+                        | None -> return positions |> Map.add nation None
+                        | Some value ->
+                            let! space = Space.fromString value
+                            return positions |> Map.add nation (Some space)
+                    })
+                (Ok Map.empty)
 
-        let pendingMovements =
-            result {
-                let mutable movements = Map.empty
-
-                for nation, pending in state.PendingMovements |> Map.toSeq do
-                    if pending.Nation <> nation then
-                        return! Error $"Pending movement nation mismatch for {nation}."
-                    else
-                        let! mapped = PendingMovement.fromContract pending
-                        movements <- movements |> Map.add nation mapped
-
-                return movements
-            }
+        let pendingMovements =
+            state.PendingMovements
+            |> Map.toSeq
+            |> Seq.fold
+                (fun current (nation, pending) ->
+                    result {
+                        let! movements = current
+                        if pending.Nation <> nation then
+                            return! Error $"Pending movement nation mismatch for {nation}."
+                        else
+                            let! mapped = PendingMovement.fromContract pending
+                            return movements |> Map.add nation mapped
+                    })
+                (Ok Map.empty)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Imperium/Rondel/State.fs` around lines 93 - 118, Replace the mutable
for-loops in the result computations for nationPositions and pendingMovements
with expression-based Seq.fold operations: for nationPositions fold over
state.NationPositions |> Map.toSeq, calling Space.fromString for Some values and
accumulating into a Map without mutability; for pendingMovements fold over
state.PendingMovements |> Map.toSeq, validate pending.Nation equals nation
(propagate Error with return! when mismatch) and call
PendingMovement.fromContract to map and add entries to the accumulator Map;
ensure you preserve the result computation context and error propagation
semantics used by Space.fromString and PendingMovement.fromContract.
src/Imperium/Rondel/Movement.fs (1)

8-14: ⚡ Quick win

Mark MoveOutcome explicitly as internal.

Please declare this as type internal MoveOutcome to align with the handler-internal type visibility rule.

🔧 Suggested change
-    type MoveOutcome =
+    type internal MoveOutcome =

As per coding guidelines: **/*.fs: Mark handler-internal types with type internal in .fs files (e.g., type internal MoveOutcome = ...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Imperium/Rondel/Movement.fs` around lines 8 - 14, Change the MoveOutcome
discriminated union to be internal by updating its declaration to "type internal
MoveOutcome = ..."; locate the MoveOutcome type in Movement.fs and prepend the
internal keyword so the union (constructors Rejected, Free,
FreeWithSupersedingUnpaidMovement, Paid, PaidWithSupersedingUnpaidMovement) is
handler-internal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Line 98: Update the stale file-layout sentence so it no longer references
"Accounting/Types.fsi/.fs" and instead describes the PR’s current module split
into Commands, Events, Dependencies, Handlers and Accounting files; keep the
rest of the sentence about the stateless skeleton and the behavior of
Handlers.chargeNationForRondelMovement (auto-approves and publishes
RondelInvoicePaid) and Handlers.voidRondelCharge intact so readers understand
the implementation and future extension points.

In `@src/Imperium/Accounting/Accounting.fsi`:
- Around line 7-9: Update the XML doc comment for the public function execute to
describe each parameter and the return semantics: document the
AccountingDependencies parameter (what dependencies are required/used), document
the AccountingCommand parameter (what kinds of commands are accepted and any
preconditions), and describe the Async<unit> return contract (what success
represents, whether exceptions are raised on failure, and any cancellation
behavior via the implicit CancellationToken). Keep the triple-slash XML style
and include <param name="..."> tags for AccountingDependencies and
AccountingCommand and a <returns> tag for the Async<unit> outcome so the .fsi
exposes full parameter and return semantics for the execute signature.

In `@src/Imperium/Rondel/Handlers.fs`:
- Around line 25-50: The four handlers (setToStartingPositions, move,
onInvoicePaid, onInvoicePaymentFailed) currently accept a LoadRondelState
parameter; change them to accept a single RondelDependencies parameter instead
and use its loader to fetch state before calling the existing logic. Concretely,
update the function signatures to take (deps: RondelDependencies), obtain the
loader from deps (e.g. deps.Load or deps.load depending on the record field name
used in your codebase), call that loader with command.GameId/event.GameId as
before, then pass the loaded state into SetToStartingPositions.execute,
Movement.execute, OnInvoicePaid.handle, and OnInvoicePaymentFailed.handle and
pipe the results to toEffects unchanged.

---

Nitpick comments:
In `@src/Imperium/Accounting/Commands.fs`:
- Around line 7-33: Add the same visual section dividers and XML documentation
comments to this implementation file to match the .fsi structure: annotate the
top-level type AccountingCommand and the record types
ChargeNationForRondelMovementCommand and VoidRondelChargeCommand with the
corresponding XML docs, and add the same commented visual divider blocks around
the "Modules" area and each module implementation
(ChargeNationForRondelMovementCommand and VoidRondelChargeCommand) so the .fs
mirrors the .fsi domain sectioning and is easier to scan.

In `@src/Imperium/Accounting/Events.fs`:
- Around line 6-22: Add the same section dividers and XML documentation present
in the .fsi to this implementation: annotate the AccountingEvent union and the
related types (RondelInvoicePaidEvent, RondelInvoicePaymentFailedEvent) with XML
doc comments and split the module into the standard visual sections (e.g.,
header, types, module functions) so the implementation mirrors the .fsi
structure; ensure the toContract function has a brief XML doc and is placed
under the expected "implementation" or "converters" divider to match coding
guidelines.

In `@src/Imperium/Rondel/Movement.fs`:
- Around line 8-14: Change the MoveOutcome discriminated union to be internal by
updating its declaration to "type internal MoveOutcome = ..."; locate the
MoveOutcome type in Movement.fs and prepend the internal keyword so the union
(constructors Rejected, Free, FreeWithSupersedingUnpaidMovement, Paid,
PaidWithSupersedingUnpaidMovement) is handler-internal.

In `@src/Imperium/Rondel/State.fs`:
- Around line 93-118: Replace the mutable for-loops in the result computations
for nationPositions and pendingMovements with expression-based Seq.fold
operations: for nationPositions fold over state.NationPositions |> Map.toSeq,
calling Space.fromString for Some values and accumulating into a Map without
mutability; for pendingMovements fold over state.PendingMovements |> Map.toSeq,
validate pending.Nation equals nation (propagate Error with return! when
mismatch) and call PendingMovement.fromContract to map and add entries to the
accumulator Map; ensure you preserve the result computation context and error
propagation semantics used by Space.fromString and PendingMovement.fromContract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 674a3c68-4a76-4265-a1a6-d074198203f1

📥 Commits

Reviewing files that changed from the base of the PR and between b7cd056 and eaf5803.

📒 Files selected for processing (43)
  • AGENTS.md
  • src/Imperium.Terminal/Accounting/Host.fs
  • src/Imperium.Terminal/Rondel/Host.fs
  • src/Imperium/Accounting.fs
  • src/Imperium/Accounting.fsi
  • src/Imperium/Accounting/Accounting.fs
  • src/Imperium/Accounting/Accounting.fsi
  • src/Imperium/Accounting/Commands.fs
  • src/Imperium/Accounting/Commands.fsi
  • src/Imperium/Accounting/Dependencies.fs
  • src/Imperium/Accounting/Dependencies.fsi
  • src/Imperium/Accounting/Events.fs
  • src/Imperium/Accounting/Events.fsi
  • src/Imperium/Accounting/Handlers.fs
  • src/Imperium/Contract/Accounting.fs
  • src/Imperium/Contract/Contract.fs
  • src/Imperium/Contract/Rondel.fs
  • src/Imperium/Gameplay/Gameplay.fs
  • src/Imperium/Gameplay/Gameplay.fsi
  • src/Imperium/Imperium.fsproj
  • src/Imperium/Primitives/AsyncExtensions.fs
  • src/Imperium/Primitives/Primitives.fs
  • src/Imperium/Rondel.fs
  • src/Imperium/Rondel.fsi
  • src/Imperium/Rondel/Commands.fs
  • src/Imperium/Rondel/Commands.fsi
  • src/Imperium/Rondel/Dependencies.fs
  • src/Imperium/Rondel/Dependencies.fsi
  • src/Imperium/Rondel/Events.fs
  • src/Imperium/Rondel/Events.fsi
  • src/Imperium/Rondel/Handlers.fs
  • src/Imperium/Rondel/Invoices.fs
  • src/Imperium/Rondel/Movement.fs
  • src/Imperium/Rondel/Queries.fs
  • src/Imperium/Rondel/Rondel.fs
  • src/Imperium/Rondel/Rondel.fsi
  • src/Imperium/Rondel/State.fs
  • src/Imperium/Rondel/State.fsi
  • src/Imperium/Rondel/Types.fs
  • src/Imperium/Rondel/Types.fsi
  • tests/Imperium.UnitTests/Accounting.fs
  • tests/Imperium.UnitTests/Rondel.fs
  • tests/Imperium.UnitTests/RondelHostTests.fs
💤 Files with no reviewable changes (4)
  • src/Imperium/Rondel.fs
  • src/Imperium/Accounting.fs
  • src/Imperium/Rondel.fsi
  • src/Imperium/Accounting.fsi

Comment thread AGENTS.md Outdated
Comment thread src/Imperium/Accounting/Accounting.fsi Outdated
Comment thread src/Imperium/Rondel/Handlers.fs
@bartul bartul merged commit 65be905 into master May 22, 2026
5 checks passed
@bartul bartul deleted the refactor/reorganize-imperium-namespaces branch May 22, 2026 21:03
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.

1 participant