[importlinter]
root_package = previously
# Seven of the eight contracts forbid external packages (sqlalchemy, psycopg,
# alembic, anthropic, openai, pyrage, boto3, botocore, html2text, and imaplib
# out of the standard library, which import-linter counts among them).
# import-linter requires this setting for that, otherwise it aborts with a
# configuration error — it is not a loosening of the contracts themselves.
include_external_packages = True

[importlinter:contract:layers]
name = Layers: cli, connectors, core beside migrations, storage, contract
type = layers
layers =
    previously.cli
    previously.connectors
    previously.core | previously.migrations
    previously.storage
    previously.contract
# `connectors` came in between `cli` and `core` on 2026-10-06, with the first
# connector: a connector speaks a foreign protocol and hands bytes to `core`,
# so it may use `core` and `core` may not know it. The name followed, because
# the name is what the gate prints.

[importlinter:contract:core-is-clean]
name = core knows no foreign system and no model
type = forbidden
source_modules =
    previously.core
forbidden_modules =
    sqlalchemy
    psycopg
    alembic
# Until 2026-10-04 this contract carried two named exemptions,
# `core.append -> storage.postgres` and `core.verify -> storage.postgres`,
# because both modules typed their `storage` parameter as the concrete
# `PostgresStorage` under `TYPE_CHECKING`. They were granted by ruling T7-a
# and by ruling T8-c of the 2026-10-02 stage 1a plan, whose execution ledger
# was never shipped and is lost, so both labels are provenance and nothing
# more — the reasoning is the paragraph you are reading.
#
# There is no directory to follow them into, and until 2026-10-04 this
# comment named one. Measured that day against the only record under
# `docs/superpowers/sdd/`: `2026-10-03-dokumentation/progress.md` carries a
# `Ruling T7-a` about an unrelated decision and no `Ruling T8-c` at all, so
# the pointer sent a reader to the wrong decision — worse than a label with
# no pointer, because the reader stops with an answer.
#
# The exemptions were enumerated by name and not matched by a wildcard, so
# that a third module following the same pattern would break this contract
# until somebody granted it deliberately — measured with a throwaway module,
# a wildcard let the new edge through silently.
#
# Stage 1b typed `core` against `contract.store.LogStore[Conn]` instead, and
# the edge is gone. There is nothing to exempt. Should an exemption ever come
# back here, it is a named edge, never a pattern; {ref}`module-boundaries`
# carries the measurement that says why.

[importlinter:contract:core-and-contract-know-no-sql]
name = core and contract import no sqlalchemy
type = forbidden
source_modules =
    previously.core
    previously.contract
forbidden_modules =
    sqlalchemy
    psycopg
# Until 2026-10-04 this contract carried the same two named exemptions as
# `core-is-clean`: edges enumerated one by one, no wildcard across `core`. See
# there for the reasoning, and for what the two stage 1a ruling labels are
# worth. Stage 1b typed `core` against `contract.store.LogStore[Conn]`, the
# edge `core -> storage.postgres` is gone, and there is nothing left to exempt
# here either.
#
# The contract was named `Only storage imports sqlalchemy`, with the id
# `only-storage-knows-sql`, until 2026-10-05. That day the migrations moved into
# the package, and they import SQLAlchemy too, so the old name stated
# something false while the contract still checked the right thing: its
# sources were always `core` and `contract`, and the name now says so.

[importlinter:contract:no-vendor-sdk]
name = No vendor SDK in the package
type = forbidden
source_modules =
    previously
forbidden_modules =
    anthropic
    openai

[importlinter:contract:only-sealing-knows-age]
name = Only core.sealing imports pyrage
type = forbidden
source_modules =
    previously
forbidden_modules =
    pyrage
ignore_imports =
    previously.core.sealing -> pyrage
# Sealing happens where the rules are, in `core`, so that the store below
# gets and gives ciphertext only: a module in `storage` that could open what
# it stores is the mistake this contract exists to catch. The whole package
# is the source, and the one edge that may exist is named — never a pattern,
# for the reason `core-is-clean` gives. Should `core.sealing` stop importing
# `pyrage`, `lint-imports` fails with "No matches for ignored import"
# (measured on 2026-10-05), so the exemption cannot outlive its edge.

[importlinter:contract:only-s3-knows-boto3]
name = Only storage.s3 imports boto3
type = forbidden
source_modules =
    previously
forbidden_modules =
    boto3
    botocore
ignore_imports =
    previously.storage.s3 -> boto3
    previously.storage.s3 -> botocore
# Storing happens where the foreign systems are, in `storage`, the way SQL
# stays in `storage.postgres`: what reaches `core` is the protocol in
# `contract.blobs` and the errors in `storage.errors`, no `botocore` type.
# `botocore` is forbidden beside `boto3` because the exceptions and the
# client configuration come from there, and an import of either would carry
# the foreign system upwards. Two named edges, out of one module.

[importlinter:contract:only-mail-knows-html2text]
name = Only core.mail imports html2text
type = forbidden
source_modules =
    previously
forbidden_modules =
    html2text
ignore_imports =
    previously.core.mail -> html2text
# Turning HTML into text is part of mapping a mail, and its version goes into
# the payload: a second module that converted HTML would write units the
# payload does not account for. The whole package is the source and the one
# edge is named, never a pattern, as with `pyrage` above.

[importlinter:contract:only-imap-knows-imaplib]
name = Only connectors.imap imports imaplib
type = forbidden
source_modules =
    previously
forbidden_modules =
    imaplib
ignore_imports =
    previously.connectors.imap -> imaplib
# Speaking IMAP is the connector's, and what it hands on is bytes and its own
# error: a second module with `imaplib` would be a second place where a
# folder could be changed, or a password could end up in a message. The whole
# package is the source and the one edge is named, never a pattern, as with
# `pyrage` above.
