[importlinter]
root_package = agentdeck
include_external_packages = True

# Enforced today: the error hierarchy is the one module every layer may import, so it
# must stay free of the execution engines and the HTTP surface.
[importlinter:contract:errors-are-engine-free]
name = errors imports no engine or surface
type = forbidden
source_modules =
    agentdeck.errors
forbidden_modules =
    agents
    langgraph
    fastapi
    redis
    psycopg

# Activated by PR #1 (the events schema). core is the innermost ring: stdlib and
# pydantic only, so the outer rings are also forbidden — importing one would invert the
# dependency direction the whole refactor rests on.
[importlinter:contract:core-is-engine-free]
name = core imports no engine, surface or outer ring
type = forbidden
source_modules =
    agentdeck.core
forbidden_modules =
    agents
    langgraph
    fastapi
    redis
    psycopg
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.runtime
    agentdeck.serve
    agentdeck.deck
    agentdeck.testing

# The Runtime is the use-case layer: it orchestrates through ports and must never reach for
# an engine, a store or a surface directly — those arrive from the composition root.
# Narrower than it looks: importing agentdeck.runtime.service still executes the v1
# agentdeck/runtime/__init__.py, which pulls in settings and workspace. Story 2 empties that.
[importlinter:contract:runtime-service-is-adapter-free]
name = the Runtime imports core only, never an adapter or a surface
type = forbidden
source_modules =
    agentdeck.runtime.service
    agentdeck.runtime.dispatch
forbidden_modules =
    agents
    langgraph
    fastapi
    redis
    psycopg
    agentdeck.adapters
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.serve
    agentdeck.deck

# These adapters wrap no external system at all (sqlite3 is stdlib), so they stay pure
# forever: a store or engine reaching back into the Runtime or a surface would invert the
# dependency direction. This is also what keeps `agents` scoped to the openai-agents
# adapter (#52): every other store/engine here is explicitly forbidden from importing it.
# The control adapters join this list for the same reason: both are sqlite3/dict only.
[importlinter:contract:pure-adapters-stay-pure]
name = the memory store, sqlite store, stub engine and control adapters import core only
type = forbidden
source_modules =
    agentdeck.adapters.stores.memory
    agentdeck.adapters.stores.sqlite
    agentdeck.adapters.engines.stub
    agentdeck.adapters.control.memory
    agentdeck.adapters.control.sqlite
forbidden_modules =
    agents
    langgraph
    fastapi
    redis
    psycopg
    agentdeck.runtime
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.serve
    agentdeck.deck

# Ring 3 (#52): surfaces render events for a Runtime handed to them by the composition
# root — they never mint an engine or a store themselves.
[importlinter:contract:surfaces-are-adapter-free]
name = surfaces import runtime and core only, never an adapter or an engine SDK
type = forbidden
source_modules =
    agentdeck.surfaces
forbidden_modules =
    agents
    langgraph
    redis
    psycopg
    agentdeck.adapters

# Checkpointer relocation (#53): langgraph is now the langgraph adapter's business, the
# same way `agents` is openai_agents's (`pure-adapters-stay-pure` above already forbids it
# for the pure stores/stub; this is the v2-wide version, for every module ADR-D5 says must
# never see a checkpointer). `agentdeck.authoring` is deliberately absent from this contract's
# source_modules — it compiles ``Workflow``/``Agent`` declarations for either engine, so it
# needs both `agents` and `langgraph` directly (amended #164: authoring/ imports core/ plus
# the engine SDKs it compiles to, not core/ alone).
[importlinter:contract:langgraph-is-engine-private]
name = only the langgraph adapter imports langgraph directly (v2 modules)
type = forbidden
source_modules =
    agentdeck.core
    agentdeck.runtime.service
    agentdeck.runtime.dispatch
    agentdeck.adapters.stores.memory
    agentdeck.adapters.stores.sqlite
    agentdeck.adapters.stores.redis
    agentdeck.adapters.stores.postgres
    agentdeck.adapters.engines.stub
    agentdeck.adapters.engines.openai_agents
    agentdeck.adapters.control.memory
    agentdeck.adapters.control.sqlite
    agentdeck.adapters.tools.mcp
    agentdeck.surfaces
    agentdeck.testing
forbidden_modules =
    langgraph

# Redis and Postgres event logs (#75): one external system each, which is the rule that
# keeps "delete any adapter directory and nothing outside it breaks" true. Two contracts
# rather than one because each store's own driver is the only one it may name — the pair
# forbids the other's, so a shared helper reaching for both would have to be a third
# module somewhere neither contract covers, and there is no such place.
[importlinter:contract:redis-store-takes-only-redis]
name = the Redis event log imports core and the Redis client only
type = forbidden
source_modules =
    agentdeck.adapters.stores.redis
forbidden_modules =
    agents
    fastapi
    psycopg
    agentdeck.runtime
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.serve
    agentdeck.deck

[importlinter:contract:postgres-store-takes-only-psycopg]
name = the Postgres event log imports core and psycopg only
type = forbidden
source_modules =
    agentdeck.adapters.stores.postgres
forbidden_modules =
    agents
    fastapi
    redis
    agentdeck.runtime
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.serve
    agentdeck.deck

# MCP relocation (#78): the raw MCP protocol SDK is the tool adapter's alone, so a second
# client can't quietly appear next to an engine or a surface. The list is everything except
# `agentdeck.adapters.tools.mcp` — including the openai-agents engine, which may import
# `agents` but gets its MCP servers as opaque handles off a `ToolSet`.
# This contract covers the `mcp` distribution ONLY. The SDK surface the adapter actually
# uses is `agents.mcp`, which import-linter cannot express (subpackages of external packages
# aren't valid contract targets) — ruff's TID251 banned-api bans that one, see pyproject.
[importlinter:contract:mcp-protocol-sdk-is-tool-adapter-private]
name = only the MCP tool adapter imports the MCP protocol SDK
type = forbidden
source_modules =
    agentdeck.core
    agentdeck.runtime
    agentdeck.adapters.stores
    agentdeck.adapters.control
    agentdeck.adapters.engines
    agentdeck.surfaces
    agentdeck.serve
    agentdeck.deck
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
forbidden_modules =
    mcp

# Langfuse as a sink (#77): tracing is a reader of the event stream, so the Langfuse SDK
# belongs to the telemetry adapter alone — an engine or a surface opening a span itself is
# what left workflows untraced in the first place, and a *second* module constructing a client
# is what made #162's span filter never apply. `langfuse` is a top-level distribution, so
# import-linter can name it directly; the MCP ban above needed ruff only because `agents.mcp`
# is a subpackage. `agentdeck.runtime` is listed whole now that `runtime/observability.py` —
# the second altitude this contract had to carve an exception for — is gone, and
# `agentdeck.authoring` joins it, since a direct-call runner opening spans of its own is what
# left #162's orphan trees. `agentdeck.composition` and `agentdeck.deck` cannot be listed: the
# composition root resolves the sink on purpose (`resolve_sinks`), so every path from them
# reaches the SDK. That one place constructs the client, and that `build_runtime` is not it,
# are pinned by name in `tests/test_observability.py` instead.
[importlinter:contract:langfuse-is-telemetry-private]
name = only the telemetry adapter imports the Langfuse SDK
type = forbidden
source_modules =
    agentdeck.core
    agentdeck.runtime
    agentdeck.authoring
    agentdeck.adapters.stores
    agentdeck.adapters.control
    agentdeck.adapters.engines
    agentdeck.adapters.tools
    agentdeck.surfaces
forbidden_modules =
    langfuse

# The sink reads events and writes spans, nothing else: deleting this directory must cost
# Langfuse traces and not one behavior more.
[importlinter:contract:telemetry-adapter-is-engine-free]
name = the telemetry adapter imports core, settings and Langfuse only
type = forbidden
source_modules =
    agentdeck.adapters.telemetry
forbidden_modules =
    agents
    langgraph
    fastapi
    redis
    psycopg
    agentdeck.adapters.control
    agentdeck.adapters.engines
    agentdeck.adapters.stores
    agentdeck.adapters.tools
    agentdeck.authoring
    agentdeck.skills
    agentdeck.mcp
    agentdeck.observers
    agentdeck.surfaces
    agentdeck.serve
    agentdeck.deck
