# Architecture contracts for import-linter (`lint-imports`), run by
# tests/test_architecture_guards.py and so by the unit suite, CI and the pre-push hook.
#
# These are RATCHETS (D-unify 4, strangler with ratchets). Each contract states the rule
# the code is moving towards; its `ignore_imports` lists the imports that break the rule
# TODAY. An entry may only be removed, never added: a new violation fails the contract,
# and an entry that no longer matches any import fails too
# (unmatched_ignore_imports_alerting = error), so the list shrinks as the code moves.
# When a refactor moves an import (a module split, a call site moved to its one home),
# edit the entry in the same change; never add one to make a new import pass.
#
# Those lists are not in this file. Each contract's `ignore_imports` lives in its own
# tests/guard_baselines/importlinter-<contract>.toml, so tasks shrinking different
# contracts' lists never edit the same file; the guard test joins them back into the
# contracts before it runs import-linter. (`lint-imports` run on this file alone reports
# every allowlisted import as broken: run the guard test instead.)
#
# Entries marked "home" are permanent: the one module allowed to own that import. A home
# module that does not exist yet (ddflow.infra.git, the extras' adapters) has no entry --
# an entry matching no import is an error -- and the task that creates the module adds its
# home entry in the same change.

[importlinter]
root_package = ddflow
include_external_packages = True
unmatched_ignore_imports_alerting = error

# surfaces > api > views > services > infra > config > core. A layer may import any layer
# below it. `config` sits above `core` because `core` is pure: it should be handed its
# knobs, not read them. `views` sits above `services`: a renderer reads service results,
# a service returns an Outcome and does not render.
[importlinter:contract:layers]
name = Layers: surfaces > api > views > services > infra > config > core
type = layers
layers =
    ddflow.surfaces
    ddflow.api
    ddflow.views
    ddflow.services
    ddflow.infra
    ddflow.config : ddflow.config_sections
    ddflow.core

# A surface (argparse, JSON-RPC) reaches the domain through ddflow.api only.
[importlinter:contract:surfaces-through-api]
name = Surfaces reach services and infra only through ddflow.api
type = forbidden
source_modules =
    ddflow.surfaces
forbidden_modules =
    ddflow.services
    ddflow.infra
allow_indirect_imports = True

# Processes start in ddflow.infra.proc (and git in ddflow.infra.git, when it exists).
[importlinter:contract:subprocess-home]
name = subprocess is imported only by ddflow.infra.proc and ddflow.infra.git
type = forbidden
source_modules =
    ddflow
forbidden_modules =
    subprocess
allow_indirect_imports = True

# Atomic writes, temporary files and locks belong in ddflow.infra.fsio.
[importlinter:contract:fsio-home]
name = tempfile and fcntl are imported only by ddflow.infra.fsio
type = forbidden
source_modules =
    ddflow
forbidden_modules =
    tempfile
    fcntl
allow_indirect_imports = True

# Content digests belong in ddflow.core.digest.
[importlinter:contract:digest-home]
name = hashlib is imported only by ddflow.core.digest
type = forbidden
source_modules =
    ddflow
forbidden_modules =
    hashlib
allow_indirect_imports = True

# Optional extras (D-unify 2): each is imported by exactly ONE adapter module, which owns
# the stdlib fallback and the doctor line. The task that writes an adapter adds its one
# "home" entry to tests/guard_baselines/importlinter-extras-one-adapter.toml, e.g.
# `ddflow.services.searchcore.fuzzy -> rapidfuzz`.
[importlinter:contract:extras-one-adapter]
name = Optional extras are imported only by their one adapter module
type = forbidden
source_modules =
    ddflow
forbidden_modules =
    rapidfuzz
    watchfiles
    model2vec
    sqlite_vec
    mcp
allow_indirect_imports = True
