# `ArchTest.Modulith`
[🔗](https://github.com/yoavgeva/arch_test/blob/v0.2.0/lib/arch_test/modulith.ex#L1)

Modulith / bounded context isolation enforcement.

Define named slices (bounded contexts) and enforce that internals of one
slice are not accessed by other slices. Only the public API module (the
root context module) may be called cross-slice.

## Slice structure

Given `define_slices(orders: "MyApp.Orders", ...)`:
- **Public API**: `MyApp.Orders` (the exact root module)
- **Internals**: `MyApp.Orders.*` and deeper (sub-modules)

## Example

    define_slices(
      orders:    "MyApp.Orders",
      inventory: "MyApp.Inventory",
      accounts:  "MyApp.Accounts"
    )
    |> allow_dependency(:orders, :accounts)
    |> enforce_isolation()

# `slice_name`

```elixir
@type slice_name() :: atom()
```

# `t`

```elixir
@type t() :: %ArchTest.Modulith{
  allowed_deps: [{slice_name(), slice_name()}],
  app: atom() | nil,
  slices: [{slice_name(), String.t()}]
}
```

# `all_modules_covered_by`

```elixir
@spec all_modules_covered_by(t(), String.t(), keyword()) :: :ok
```

Asserts that every module under `namespace_pattern` belongs to a declared slice.

Any module that does not match any slice's namespace is a violation. This
prevents new modules from silently escaping slice coverage.

## Options

- `:except` — list of glob patterns to exclude from the check
- `:graph` — pre-built dependency graph (useful for testing, avoids xref)

## Example

    define_slices(auth: "Vireale.Auth", feeds: "Vireale.Feeds")
    |> all_modules_covered_by("Vireale.**",
         except: ["Vireale.Application", "Vireale.Repo"])

# `allow_dependency`

```elixir
@spec allow_dependency(t(), slice_name(), slice_name()) :: t()
```

Permits `from_slice` to call the public API (root module) of `to_slice`.

Without this, cross-slice calls to internal sub-modules are always
violations, and calls to other slices' public root modules are also
violations by default.

With `allow_dependency(:orders, :accounts)`, `MyApp.Orders.*` may call
`MyApp.Accounts` (but not `MyApp.Accounts.Repo`, etc.).

# `define_slices`

```elixir
@spec define_slices(keyword()) :: t()
```

Defines named slices (bounded contexts).

Accepts a keyword list of `slice_name: "RootNamespace"` pairs.

# `enforce_isolation`

```elixir
@spec enforce_isolation(t()) :: :ok
```

Enforces that slice internals are not accessed by other slices.

Rules:
1. Module A (in slice X) calling Module B (in slice Y's internals) is a
   violation, unless `allow_dependency(X, Y)` has been declared.
2. Even with `allow_dependency(X, Y)`, only the public root module of Y
   may be called (not sub-modules).

# `for_app`

```elixir
@spec for_app(t(), atom()) :: t()
```

Sets the OTP application to introspect (default: `:all`).

# `should_be_free_of_cycles`

```elixir
@spec should_be_free_of_cycles(t()) :: :ok
```

Asserts that there are no circular dependencies between slices.

Each slice is treated as a single node; a cycle exists when slice A
depends on slice B which (transitively) depends on slice A.

# `should_not_depend_on_each_other`

```elixir
@spec should_not_depend_on_each_other(t()) :: :ok
```

Asserts that slices have absolutely no cross-slice dependencies.

This is stricter than `enforce_isolation/1` — not even the public root module
of another slice may be called. Use this for completely independent bounded contexts.

## Example

    define_slices(
      orders:    "MyApp.Orders",
      inventory: "MyApp.Inventory",
      accounts:  "MyApp.Accounts"
    )
    |> should_not_depend_on_each_other()

---

*Consult [api-reference.md](api-reference.md) for complete listing*
