Ctrl K
ComunidadGuías…

backend/pyproject.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[project]
name = "agenticos"
version = "1.65.1"
description = "OS for your agents."
requires-python = ">=3.15"
license = { text = "Apache-2.0" }
authors = [
    { name = "DEENUU1", email = "kacper.wlodarczyk@vstorm.co" },
]

dependencies = [
    # Uncapped on purpose. FastAPI, Pydantic AI, Logfire and genai-prices track
    # the newest release: this platform's whole subject matter moves at their
    # pace, and genai-prices in particular *is* the model price snapshot, so an
    # old one means budgets computed from stale prices. `make deps-upgrade`
    # bumps them, the `dependency-freshness` workflow tries the newest of
    # everything on a schedule, and Dependabot opens the PR. A cap here (it was
    # `<0.137`) turns all three into no-ops.
    # The floor is a correctness floor, not a taste one: `Depends(..., scope=)`
    # is what makes the request's transaction commit before the response is sent
    # (#353), and an older FastAPI answers `TypeError` at import rather than
    # quietly reverting - but only 0.141.1 has been run against this suite.
    "fastapi>=0.143.2",
    "uvicorn[standard]>=0.54.0",
    "pydantic[email]>=2.14.3",
    "pydantic-settings>=2.16.2",
    "python-multipart>=0.0.33",
    "logfire[fastapi,asyncpg,redis,httpx]>=5.4.0",
    "sqlalchemy[asyncio]>=2.1.8",
    "asyncpg>=0.32.0",
    # Alembic's, not the application's. Every application URL is
    # `postgresql+asyncpg`, so this reads as dead to a grep - but `alembic/env.py`
    # builds a *sync* engine from `DATABASE_URL_SYNC`, which is
    # `postgresql+psycopg2://`. Removing it breaks every migration (#155). Moving
    # Alembic to asyncpg was tried for 9 MB of image (#2219) and dropped: asyncpg
    # is strict about bind types where psycopg2 is not, and the backfills that
    # would meet that run only against data no local check has.
    "psycopg2-binary>=2.9.13",
    "alembic>=1.19.2",
    "greenlet>=3.5.5",
    "pyjwt>=2.15.1",
    "bcrypt>=4.0.0",
    "authlib>=1.9.5",
    # Imported at module scope in `app/services/rag_document.py`, and until #155
    # declared only in the `dev` group - so `uv sync --no-dev`, which is what the
    # image builds with, was relying on Starlette to pull it in.
    "anyio>=4.16.0",
    "redis>=8.1.0",
    "aiosmtplib>=5.1.3",
    # Starlette's requirement for `SessionMiddleware`, which `main.py` adds for
    # the OAuth handshake. Declared here rather than left transitive because the
    # middleware is ours: Starlette itself installs without it (#155).
    "itsdangerous>=2.2.0",
    # The floor is ours as well as `pydantic-ai-harness`'s: the sandbox runs on
    # `pydantic_ai.workspaces` and passes `workspace=` to a run, both from 2.52,
    # and stating it keeps a resolver from picking a slim the harness cannot run
    # on. Dependabot raises it to
    # whatever is current, which is fine as long as the lock resolves to at least
    # as new: this group's bump once locked 2.30.0 while `main` already held
    # 2.31.0, and the image installs the lock verbatim (`uv sync --frozen`).
    "pydantic-ai-slim[openrouter,anthropic,google,duckduckgo,web-fetch,groq,mistral,cohere,huggingface,xai,openai-realtime,google-realtime,xai-realtime]>=2.66.0",
    "genai-prices>=0.2.1",
    "pydantic-ai-slim[mcp]>=2.66.0",
    # The MCP SDK. Uncapped since #1820 moved the one path where our code and
    # the SDK exchange HTTP objects onto `httpx2`, which 2.0 migrated the whole
    # SDK to: `PinnedAsyncClient` (`app/core/pinned_http.py`) and the MCP OAuth
    # flow are `httpx2` now, so an SDK-built request reaches a client that can
    # send it and a malformed endpoint raises an exception our refusals catch.
    #
    # **Floored at 2, not merely uncapped.** `app/agents/mcp.py` imports
    # `create_mcp_http_client` and `streamable_http_client` unconditionally, and
    # both are 2.x names - 1.x spelled the transport `streamablehttp_client` and
    # had no client factory at all. A non-frozen install that resolved back to
    # 1.x would fail every streamable probe at import time; the image installs
    # the lock verbatim, but nothing else does.
    #
    # The rest of this codebase stays on `httpx` deliberately. The boundary is
    # wherever an object crosses into or out of a vendor SDK, and nothing else
    # here does: the portals, the channels, `model_catalog` and the web-research
    # search all build and send their own requests.
    "mcp>=2.3.0",
    # The platform's own MCP server and its OAuth endpoints (#2058, #2059).
    "fastmcp>=4.1.6",
    # Wrapped by three capabilities: `guardrails` (the ready-made secret/PII
    # detectors and the input/output/tool guard edges), `planning` (the plan
    # store, tools and cache-safe tail reminder) and `compaction` (the
    # context-window handling and its strategies). Uncapped for the same reason
    # Pydantic AI is: this is where those are maintained, and pinning would
    # freeze the detectors and the model registry they read. Every agent also
    # wraps RepairToolArguments, whose json-repair dependency is selected by
    # its capability extra in the 0.64 release.
    "pydantic-ai-harness[e2b,repair-tool-arguments]>=0.66.0",
    "websockets>=17.4",
    # Required, not optional: hybrid retrieval fuses BM25 with the vector search
    # and a deployment cannot choose otherwise. It is also the only reason
    # `numpy` (24 MB) is installed - a real cost, taken deliberately (#155).
    "rank-bm25>=0.2.2",
    "pymupdf>=1.28.2",
    # `app/services/channels/chart_png.py` renders a chart tool's output for a
    # channel that cannot show HTML. A transitive of the parsers as well, but the
    # direct use is ours.
    "pillow>=10.0.0",
    "python-docx>=1.1.0",
    "openpyxl>=3.1.0",
    "llama-cloud>=2.18.0",
    "liteparse>=2.18.0",
    # `html.parse` reads markup with bs4's standard-library parser and picks
    # elements with soupsieve. Both already ship through `pydantic-ai-slim`'s
    # web-fetch extra; declared here so a release of it cannot drop them.
    "beautifulsoup4>=4.15.0",
    # Soup Sieve 4 hides template contents from document selectors; preserve
    # html.parse links and matches until that major change is supported.
    "soupsieve>=2.9.2,<4",
    # Google Drive speaks plain REST through `app/services/rag/drive_api.py`;
    # `google-auth` only signs the service account's token, over `requests`.
    # `google-api-python-client` was 108 MB of discovery documents for four URLs.
    "google-auth[requests]>=2.62.1",
    "boto3>=1.43.126",
    # Two HTTP libraries, deliberately. `httpx` is what fourteen modules build
    # and send their own requests with - the portals, the channels, the model
    # catalog, the web-research search - and `httpx2` is what the MCP SDK is
    # written against since 2.0. Declared rather than inherited through `mcp`
    # because `app/core/pinned_http.py` imports it directly: the boundary is
    # wherever an object crosses into or out of a vendor SDK, and a library our
    # own code imports is one this manifest names (#1820).
    "httpx[http2]>=0.28.1",
    "httpx2>=2.13.1",
    "click>=8.5.1",
    # `cli/commands.py` renders every list the CLI prints. It has no import site
    # under `app/`, which is how it was briefly deleted as dead (#155) - the
    # `deptry` step reads `cli/` as well for exactly that reason.
    "tabulate>=0.9.0",
    # Renders a Markdown artifact into the page it is served as. Already here
    # through `rich`; declared because `app/services/artifact.py` imports it.
    "markdown-it-py>=4.0.0",
    "tqdm>=4.70.1",
    "tavily-python>=0.8.5",
    "pydantic-monty>=1.5.0",
    # Floors at 2.0, where each skill became its own deferred capability. The
    # platform's three skills tools collapsed to one: the catalog is the model's
    # own capability list, `load_capability` opens a skill, and only
    # `read_skill_resource` survives as a tool (#1658).
    "pydantic-ai-skills>=2.1.1",
    # `console` for the tools, `workspaces` for the workspace capabilities,
    # `remote` for the Files panel's archive client and `daytona` for Daytona
    # connections (the `daytona` package; the old `daytona-sdk` never imported).
    # Deliberately not `docker`: this container never touches the socket, so the
    # Docker SDK has no business in the image. The service runs beside us as
    # `sandboxd`. Floors at 0.2.32 for named sandboxd sessions and Daytona
    # sandboxes, which a workspace keyed on its scope opens or attaches by name
    # (#2003).
    "pydantic-ai-backend[console,remote,workspaces,daytona]>=0.2.39",
    # Delegation. Floors at 0.2.16 for three fixes this integration depends on:
    # a suspended child no longer reported to the parent as completed (#64),
    # `event_stream_handler_factory` on the toolset so a fan-out's events can be
    # labelled per delegation (#65), and a bounded wait in `cancel_all`'s
    # `finally` so a child that swallows cancellation cannot hang our teardown
    # (#66).
    # 0.2.17 first carried the caller-supplied-subagent ask_parent fix a sync
    # delegate needs to reach the person waiting on its parent
    # (subagents-pydantic-ai#76); 0.2.18 also fixes the general-purpose delegate
    # (#78). 0.2.19 adds `TaskHandle.exception`, which is what keeps a provider's
    # own text off a delegated run's row (#699). 0.2.20 reads that field in
    # `check_task` and `wait_tasks` themselves, so a status answer names the
    # exception's class rather than embedding the provider's message - the one
    # surface `tool_retry_notice` cannot reach, because a `ToolReturnPart` is
    # stored whole (#819). 0.2.22 puts the subagent's name on the delegation's
    # bound state, which is the only way a persisted `ask_user` part can say
    # which delegate asked - `ask_parent` hands the surface the question and
    # nothing else (#1042). 0.2.25 runs a delegate in its parent's workspace,
    # which is how a delegate sharing the parent's `sandbox` reaches the same
    # files now that the files live in `ctx.workspace` (#2003). The floor is
    # 0.2.25.
    "subagents-pydantic-ai>=0.2.25",
    "cryptography>=50.0.2",
    "aiogram>=3.31.0,<4.0",
    "slack-sdk>=3.46.0",
    # What Slack Socket Mode actually needs. `app/services/channels/slack.py`
    # imports `slack_sdk.socket_mode.aiohttp` - the transport a bot uses when the
    # deployment exposes nothing - and slack-sdk keeps aiohttp behind an extra, so
    # a plain `slack-sdk` does not pull it. It resolved anyway, because something
    # else in the tree happened to bring aiohttp; the day that goes, the import
    # raises inside a `try/except ImportError` that turns it into a bot which
    # quietly never starts rather than a build that fails.
    # Not `slack-sdk[optional]`, which is a grab bag: aiodns, boto3, SQLAlchemy,
    # websockets and websocket-client for one transport's HTTP client. And not
    # `slack-sdk[socket-mode]`, which does not exist - the only extra is
    # `optional`, so the advice in that ImportError's message was unfollowable.
    "aiohttp>=3.14.5",
    # Cron schedules: the next fire after a given instant for a crontab
    # expression. Interval schedules need no library, but "daily/weekly at HH:MM"
    # and any raw crontab do - and getting DST and month lengths right by hand is
    # the kind of arithmetic a dependency exists to own.
    "croniter>=3.0.0",
    "xlrd>=2.0.2",
    "odfpy>=1.4.1",
    "python-pptx>=1.0.2",
    "olefile>=0.47",
    # Signing in with a directory account (#1773): the two-step bind against
    # Active Directory, OpenLDAP or FreeIPA. Pure Python, so the image needs no
    # libldap and no compiler for it, which is why it and not `python-ldap` or
    # `bonsai`. LGPL-3.0, used unmodified and replaceable by reinstalling - the
    # decision is recorded in `licenses/policy.toml`.
    "ldap3>=2.9.1",
    # The expression language workflow `data.map` and `logic.if` nodes evaluate:
    # declarative JSON queries with no assignment, loops or user functions, so a
    # graph never executes code (#1789). MIT; already installed through boto3.
    "jmespath>=1.1.0",
    # Checks an `agent.run` step's structured answer against the JSON Schema the
    # step declares, before anything downstream reads it (#1789). MIT; already
    # installed through the MCP SDK.
    "jsonschema>=4.26.0",
    # An explicit registry disables remote reference fetching in MCP schemas.
    "referencing>=0.37.0",
    # The `guardrails` phone detector, which the harness's PII patterns lack. The
    # lite build: the same `phonenumbers` module and numbering-plan metadata,
    # without the 20 MB of carrier, geocoding and time zone data nothing here reads.
    "phonenumberslite>=9.0.43",
    "regex>=2026.9.10",
    # The ingest pool's memory watchdog (#2219): the resident size of a job's child
    # process and of whatever it starts - LibreOffice for a conversion - so a parse
    # that grows past its limit is stopped and the parent says why. `/proc` alone
    # would do on Linux and nowhere else.
    "psutil>=7.0",
    # Web Push to browsers and installed PWAs (#2097): the message encryption
    # (RFC 8291) and the VAPID signature (RFC 8292) every push service demands.
    # MPL-2.0, reviewed in licenses/policy.toml.
    "pywebpush>=2",
    "pydantic-ai-otari>=0.1.0,<0.2",
]

[project.optional-dependencies]
# The web browser's one engine dependency (`browser_use`, #2127). The loop is this
# repository's and the browsing model is the run's own, so all a library does is
# speak CDP.
#
# `cdp-use` is a typed, async client for the whole Chrome DevTools Protocol and
# nothing else: `client.send.Accessibility.getFullAXTree(...)`,
# `Page.captureScreenshot`, `Input.dispatchMouseEvent`. It ships `py.typed`, and
# it carries `httpx`, `typing-extensions` and `websockets>=15.0.1` - floors, not
# pins - so the extra costs this lock no downgrade at all.
#
# **Not `browser-use` itself.** 0.13.x hard-pins `openai`, `google-genai` and
# `requests` below what Pydantic AI and this project need, and uv refuses to lock
# even an optional extra whose pins conflict with the base ones. Its approach -
# one structured step per model call over a numbered element table - is what
# `_decision.py` does, on the run's model.
#
# **Not `browser-harness`**: a CLI and a per-user daemon with synchronous helpers,
# built to attach a coding agent to a developer's own Chrome, not a library for a
# multi-tenant service.
browser = [
    "cdp-use==1.4.5",
]
# Integrated Windows sign-in (SPNEGO), the `KERBEROS_ENABLED` half of #1773.
# `gssapi` publishes no Linux wheel: it compiles against the system Kerberos
# headers, which is why it is an extra rather than a dependency every install and
# every CI job would have to build. The image takes it with
# `--build-arg EXTRAS="--extra kerberos"`, and the Dockerfile installs
# `libkrb5-dev` to build and `libgssapi-krb5-2` to run only for that build.
kerberos = [
    "gssapi>=1.9.0",
]
# Mirage is optional; its exact pin is shared with the test environment.
# The S3 family remains unavailable because aioboto3 pins an incompatible boto3.
mirage = [
    "mirage-ai[pydantic-ai,ssh,mongodb,gridfs,postgres,redis,email,nextcloud,hf-buckets,chroma,mem0,langfuse,qdrant,lancedb]==0.0.8",
]

# PEP 735 dependency group — installed by `uv sync` / `uv sync --dev` and
# excluded by `--no-dev`. (A `dev` extra under [project.optional-dependencies]
# only installed via uv's deprecated dev-extra special-casing, so `uv sync --dev`
# could skip it on some uv versions and leave pre-commit/ruff/ty unspawnable.)
[dependency-groups]
dev = [
    # The `mirage` extra's pin, for the suite - see the extra.
    "mirage-ai[pydantic-ai,ssh,mongodb,gridfs,postgres,redis,email,nextcloud,hf-buckets,chroma,mem0,langfuse,qdrant,lancedb]==0.0.8",
    "pytest>=9.2.0",
    "anyio[trio]>=4.16.0",
    "pytest-cov>=6.1.0",
    "httpx>=0.28.0",
    "ruff>=0.17.4",
    "ty>=0.0.99",
    "pre-commit>=4.7.0",
    "pytest-xdist>=3.8.0",
    # Shuffles the suite every run and prints the seed, so an order-dependent
    # test fails here rather than months later in an unrelated pull request.
    # It carries its own xdist support (the controller's seed is handed to each
    # worker through `workerinput`), without which the workers would collect in
    # four different orders and `-n auto` would abort on the mismatch.
    "pytest-randomly>=5.0.0",
    "vulture>=2.14",
    # vulture reads the code and finds what is written but unused; deptry reads
    # the manifest and finds what is declared but unimported. Nothing noticed
    # `fastapi-cache2` or `jinja2` going dead, which is the half of #155 worth
    # more than the megabytes it recovered.
    "deptry>=0.23.0",
    # `ldap3` ships no annotations; typeshed's stubs are what lets the adapter
    # over it be held to the platform layer's type gate.
    "types-ldap3>=2.9.13",
    # The public API's contract test validates real responses against the
    # schemas its OpenAPI document publishes (#1796). It already arrives
    # through the MCP SDK; declared so the test does not lean on that.
    "jsonschema>=4.26.0",
]
# The documentation site. Its own group rather than part of `dev`, because the
# reference pages import the application to read its docstrings - so this group
# needs the runtime installed, and CI's lint job does not need mkdocs.
docs = [
    "mkdocs>=1.6.0",
    "mkdocs-glightbox>=0.5.2",
    "mkdocs-material>=9.5.0",
    "mkdocstrings[python]>=1.0.8",
    # One build per locale from `<page>.<locale>.md` files sitting beside the
    # English source, so `/install/` keeps its URL and `/pl/install/` is added
    # rather than everything moving under `/en/`.
    "mkdocs-static-i18n>=1.2.3",
]

[project.scripts]
agenticos = "cli.commands:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["app", "cli"]

# Dead-code detection. Two modes share this config:
#   `make lint` runs `vulture` at the default confidence below - a blocking gate,
#   but only on what vulture is sure of (unused variables, parameters, imports).
#   Unused *functions and methods* sit at 60% confidence here because the
#   codebase is registry-driven: FastAPI route handlers, Pydantic validators and
#   capability-protocol methods are all called by name from a framework vulture
#   cannot see, so a blocking function-level gate would be false positives all
#   the way down. `make dead-code` lowers the confidence to 60 for a periodic,
#   human-read scan instead - the same role `dependency-freshness` plays.
# `ignore_decorators` removes the framework false positives from both modes;
# `ignore_names` is the allowlist for anything left that is reachable in a way
# vulture cannot follow - add `name  # why` when a real false positive appears.
[tool.vulture]
paths = ["app"]
min_confidence = 80
ignore_decorators = [
    "@field_validator",
    "@model_validator",
    "@field_serializer",
    "@model_serializer",
    "@computed_field",
    "@router.*",
    "@*.get",
    "@*.post",
    "@*.put",
    "@*.patch",
    "@*.delete",
    "@*.websocket",
]
ignore_names = []

# The other half of vulture: vulture finds code nothing calls, deptry finds a
# distribution nothing imports. DEP002 is the gate this was added for - that is
# the failure this repository has actually had, with `fastapi-cache2` and
# `jinja2` sitting declared and unimported until somebody read all 46 lines by
# hand (#155).
#
# It reads `app`, `cli` and `alembic`, which is every tree that ships, and the
# scope is the whole point rather than a detail: reading `app` alone called
# `tabulate` dead when `cli/commands.py` imports it, which took the e2e seed
# down on this very branch. A tree that ships and is not scanned is a tree whose
# imports do not count.
#
# Two rules are off, each for its own reason, and turning either on is its own
# change rather than a flag flip:
#   DEP001 (imported, undeclared) fires on `faker` and `resend`, neither declared
#     anywhere and neither in `uv.lock`. `faker` is guarded; `resend` is not, so
#     a deployment that configures it raises `ModuleNotFoundError` at first send.
#     That is #829, and the decision there is whether they become extras - not
#     whether deptry shouts.
#   DEP003 (imported, only transitive) fires 27 times on `starlette`, `mcp`,
#     `yaml`, `openai`, `botocore`, `ddgs` and `opentelemetry` - every one of
#     them arriving through a declaration we do control (`fastapi`, the
#     `pydantic-ai-slim` extras, `boto3`, `logfire`). Declaring each by hand
#     would double the manifest to restate what those extras already promise.
#
# DEP004 (imported, declared dev-only) is the second gate: it found `anyio`,
# imported at module scope in application code and declared only in the `dev`
# group, and there is nothing left for it to be noisy about.
[tool.deptry]
known_first_party = ["app", "cli"]
ignore = ["DEP001", "DEP003"]

[tool.deptry.per_rule_ignores]
# Required, and imported nowhere in the scanned trees on purpose. Each is either
# a runtime somebody else imports or a driver a URL scheme selects.
DEP002 = [
    "python-multipart", # Starlette parses uploads with it; FastAPI refuses `File(...)` without it
    "asyncpg",          # selected by the `postgresql+asyncpg://` URL
    "psycopg2-binary",  # selected by Alembic's `postgresql+psycopg2://` URL
    "greenlet",         # SQLAlchemy's async bridge
    "itsdangerous",     # Starlette signs `SessionMiddleware` cookies with it
    "aiohttp",          # `slack_sdk.socket_mode.aiohttp` imports it; nothing here does
]

[tool.ruff]
target-version = "py315"
line-length = 100
# These run on the runner's own `python3` (3.12), before any toolchain is
# installed - CI's first job, and the dependency-graph submission - so they keep
# to syntax that interpreter parses.
per-file-target-version = { "../deploy/updater/*.py" = "py312", "../scripts/ci_changed_scope.py" = "py312", "../scripts/dependency_snapshot.py" = "py312" }

[tool.ruff.lint]
# Dotless i is a distinct Turkish letter used in localized messages.
allowed-confusables = ["ı"]
# The rule set is deliberately wide. A linter that only catches formatting is a
# linter people stop reading; these are the categories that catch real defects
# in an async, multi-tenant service — swallowed exceptions, blocking calls in a
# coroutine, mutable defaults, SQL built by string concatenation.
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "B",    # flake8-bugbear — mutable defaults, unused loop vars, except sequencing
    "C4",   # flake8-comprehensions
    "UP",   # pyupgrade
    "SIM",  # flake8-simplify
    "RUF",  # ruff-specific
    "ASYNC",# flake8-async — blocking calls inside coroutines
    "S",    # flake8-bandit — the security rules that are not noise
    "A",    # flake8-builtins — shadowing `id`, `type`, `list`
    "DTZ",  # flake8-datetimez — naive datetimes, which corrupt spend windows
    "T20",  # flake8-print — stray print() in a service
    "LOG",  # flake8-logging — logger misuse
    "G",    # flake8-logging-format — f-strings in log calls
    "PIE",  # flake8-pie
    "RET",  # flake8-return
    "TID",  # flake8-tidy-imports
    "PTH",  # flake8-use-pathlib
    "ERA",  # eradicate — commented-out code
    "TRY",  # tryceratops — swallowed and over-broad exception handling
]
ignore = [
    "E501",   # line length is the formatter's job
    "E402",   # module-level import not at top — the template's conditional imports
    "B008",   # call in a default argument — this is how FastAPI Depends works
    "B905",   # zip() without strict=
    "RUF012", # mutable class attribute without ClassVar
    "RUF059", # unpacked variable never used
    "TRY003", # long message outside the exception class — our errors carry context
    "TRY400", # logger.error vs logger.exception — both are used deliberately
    "S101",   # assert — used in tests, and allowed there by the per-file rules
    # S105/S106 fire on any identifier containing "token", "password" or
    # "secret". In an auth service that is `token_type="bearer"`, an enum member
    # named TOKEN, and a help string called token_hint. The signal-to-noise
    # ratio makes the rule unreadable rather than useful; real secret handling
    # is covered by the vault tests, which assert no key reaches a response.
    "S105",
    "S106",
    # Our own async surface is fully typed; ASYNC109/240 fire on the template's
    # RAG pipeline, where a `timeout` parameter and pathlib are deliberate and
    # rewriting them is a separate piece of work.
    "ASYNC109",
    "ASYNC240",
]

[tool.ruff.lint.per-file-ignores]
# Tests legitimately do things production code must not: assert, hardcoded
# credentials in fixtures, reaching into private helpers to pin a branch.
# Alembic and the repository-root guard scripts came under ruff in #229 (before
# it, both were outside the paths ruff was invoked on and these ignores were dead
# config). Autogenerated migrations carry a commented-out downgrade stub (ERA001)
# and raw SQL passed to `op.execute` (S608); the usual structural rules make a
# migration harder to read, not easier.
"alembic/**" = ["ERA001", "S608"]
# The repository-root `scripts/` are console guards run by `make lint` and a Stop
# hook: printing to stdout is their whole purpose (T201), and `docs_drift.py`
# shells out to `git` by name with a literal argv it builds itself (S603, S607) -
# the same partial-path invocation already accepted for `tests/test_migrations.py`.
"../scripts/**" = ["T201", "S603", "S607"]
# The load harness is a console tool with the same shape: it prints its progress
# and its report to stdout (T201), because a load run is watched while it runs and
# its output is redirected into a results file afterwards. It is not imported by
# the application - nothing under `app/` may import it - and it carries no gate.
"../loadtest/**" = ["T201"]
# Never run in the API: a sandbox runs its source with `python3 -c` and the
# computerd image copies it in, as the desktop's own account. Its answer is its
# output (T201), and it runs the desktop's `xdotool` and `exo-open` by name with
# an argv it builds itself, the entry path an argument (S603, S607).
"app/agents/capabilities/computer_use/_desktop_apps.py" = ["T201", "S603", "S607"]
# Postgres cannot parameterise an identifier, so a table name must be
# interpolated. `_validate_collection_name` rejects anything but
# [a-zA-Z0-9_] before it reaches a query, and every *value* is bound. This is
# the correct pattern, not an exception to it.
"app/services/rag/vectorstore.py" = ["S608"]
# The memory facts recall is the same shape: the interpolated pieces are the
# halfvec cast (built from an int dimension) and a fixed `IS NULL` / `= :scope`
# choice, and every value is bound. The vector column has no SQLAlchemy type, so
# this is the only way to run the KNN, not an exception to the bound-value rule.
"app/repositories/memory.py" = ["S608"]
# FastAPI exception handlers run while sys.exc_info() is still set, so
# logger.exception() captures the traceback correctly here despite not being
# lexically inside an `except` block.
"app/api/exception_handlers.py" = ["LOG004"]
# "id" shadows the builtin, but it is the registry's vocabulary and what agent
# specs store. Any other name would read worse at every call site.
"app/agents/capabilities/_registry.py" = ["A002"]
# Fixture helpers name their parameter `id` to mirror the model they stand in
# for; renaming it would make the fixtures read less like the thing they mock.
# Fixture and stub classes name their parameter `id` to mirror the model they
# stand in for; renaming it would make them read less like the thing they mock.
# Also relaxed here: the style rules whose whole value is readability in long
# production functions, which short test bodies do not have.
"tests/**" = [
    "S105",
    "S106",
    # A default like `password="secret"` in a fixture helper is the fixture, not
    # a leak. Real secret handling is asserted in tests/test_vault.py.
    "S107",
    "S311",
    # tests/test_migrations.py shells out to alembic on purpose; the "untrusted
    # input" is a literal argv this file wrote.
    "S603",
    # tests/test_ssrf.py names 0.0.0.0 because refusing to reach it is the
    # behaviour under test.
    "S104",
    "A002",
    "TRY300",
    "RET504",
    "PLR2004",
    "SLF001",
]
# The RAG pipeline came with the generator. `filter` as a parameter name, md5
# as a cache key (not a security primitive) and two blocking reads inside
# third-party SDK calls are its debt, tracked rather than silently accepted:
# they move out of this list when that subsystem is rewritten.
"app/services/rag/retrieval.py" = ["S324"]
"app/services/file_upload.py" = ["TRY300", "PTH123", "PTH108"]
# Template-inherited subsystems. These are readability rules whose value is
# real but whose cost right now is churn in code we did not design and are not
# yet responsible for. They lift file by file as each subsystem is rewritten —
# the list shrinking is the progress marker.
"app/core/sanitize.py" = ["PTH119", "S107"]
"app/services/agent.py" = ["TRY300", "TRY301"]
"app/services/channels/telegram.py" = ["TRY300", "S107"]
"app/services/rag/ingestion.py" = ["TRY300", "PTH123"]
"app/services/rag/documents.py" = ["PTH123"]
"app/worker/tasks/rag_tasks.py" = ["TRY300"]
"app/clients/redis.py" = ["TRY300"]
"app/core/security.py" = ["TRY300"]
"app/commands/seed.py" = ["S311"]
# A polling loop that exits on a flag rather than an Event. Same tracked debt as
# the rest of the generator's channel code.
"app/services/channels/slack.py" = ["ASYNC110"]
# The CLI binds 0.0.0.0 because that is what a container needs, and its
# command bodies read fine as try/except/return.
"cli/**" = ["S104", "TRY300"]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
# Annotations are evaluated lazily since Python 3.14 (PEP 649), so this import
# changes nothing but turns every annotation into a string - which a library
# reading annotations at runtime then has to resolve itself (#2229).
"__future__.annotations".msg = "annotations are lazy on Python 3.14+; drop the import"

[tool.ruff.lint.isort]
known-first-party = ["app", "cli"]

[tool.ty.terminal]
# The "warn" rules below are downgraded because the template's own, untyped-by-
# design dependencies trip them - not because a finding there is safe to ignore.
# `error-on-warning = true` means a warning gates the run exactly like an error
# does; the platform-layer override further down still promotes the same rules
# to `error` for our own code, so a violation there is the more severe
# annotation (red, not yellow) even though both now fail the run the same way.
# `false` here is what let 61 warning-level diagnostics exit 0 and look
# identical to a clean run - green check, no annotation, nothing distinguishing
# the two.
error-on-warning = true

[tool.ty.environment]
python-version = "3.15"

[tool.ty.src]
# `app/commands/` used to be here, which quietly took `app/commands/bootstrap.py`
# — a gated platform module — out of the checker altogether. An exclusion that
# hides one of the files the gate is supposed to cover is worth 23 warnings from
# the generator's other commands.
exclude = [
    "alembic/",
    "tests/",
    "cli/",
]

[tool.ty.rules]
# FastAPI Depends() injects at runtime — default=None on typed params is intentional
invalid-parameter-default = "warn"
# Starlette/FastAPI handler signatures use Union types that ty flags
invalid-argument-type = "warn"
# ORM models with Optional fields trigger on attribute assignment
invalid-assignment = "warn"
# Callable[..., Any] doesn't expose __name__ but function objects do
unresolved-attribute = "warn"
# datetime.utcnow deprecation warnings
deprecated = "warn"
# Optional deps imported inside try/except (pymupdf, docx, faker) and
# feature-gated models referenced best-effort by the admin dashboard. These are
# expected to be absent in some configs, so don't surface them at all.
import-not-found = "ignore"
# dict() overload matching on dynamic types from validators
no-matching-overload = "warn"
# Template uses mypy-style `# type: ignore` comments which ty doesn't recognize
unused-type-ignore-comment = "warn"
# A `# ty: ignore` needed in one database/feature config can be unused in
# another generated from the same template — don't fail on that.
unused-ignore-comment = "ignore"
# Operations on Unknown types from untyped libraries (pymupdf, etc.)
unsupported-operator = "warn"
# redis-py / asyncio overloads return Awaitable[T] | T which ty rejects as awaitable
invalid-await = "warn"
# sessionmaker(class_=AsyncSession) returns sync sessionmaker per overloads
# but the resulting callable still produces an async context manager.
invalid-context-manager = "warn"
# Helpers like get_agent accept **kwargs but their stubs only list specific params
missing-argument = "warn"

# Every rule above is downgraded because of a library we did not choose and do
# not own. None of those reasons apply to the code we wrote, so the platform
# layer — everything AgenticOS adds on top of the generated template — gets them
# back as errors, and `ty check` can fail a build again.
#
# The list below is `[tool.coverage.run] include`, verbatim and in the same
# order. It is the same definition of "ours" for both gates on purpose: a module
# held to 100% coverage is held to the type checker too, and a module added to
# one list belongs in the other. `tests/test_coverage_gate.py` keeps the two
# from drifting.
[[tool.ty.overrides]]
include = [
    "app/agents/**",
    "app/commands/bootstrap.py",
    "app/commands/refresh_assistant.py",
    "app/commands/audit_skill_bindings.py",
    "app/commands/audit_verify.py",
    "app/commands/vault_rotate.py",
    "app/commands/sandbox_runtimes.py",
    "app/commands/invite_members.py",
    "app/commands/data_protection.py",
    "app/commands/mcp_audit.py",
    "app/core/permissions.py",
    "app/core/catalog/**",
    "app/core/secret_kinds.py",
    "app/core/tool_identity.py",
    "app/core/vault.py",
    # The audit trail, fail-closed and part of the caller's transaction. Every
    # service that records a privileged action is already in this list; the trail
    # they write to was not (#20).
    "app/core/audit.py",
    "app/core/background.py",
    # The dedicated bounded pool for blocking file work, kept off the executor
    # bcrypt and DNS share; its write is cancellation-safe (#1108).
    "app/core/blocking.py",
    # The LibreOffice conversion the RAG parser (and, later, chat) drives, owned
    # here so a hung `soffice` is killed on timeout rather than orphaned (#1685).
    "app/core/office_convert.py",
    # The SSRF allow/deny every outbound URL a tenant chooses is checked against -
    # a webhook, an MCP server address. A hole here is a request to an internal
    # address the operator cannot see, which is the pair to pinned_http below (#28).
    "app/core/sanitize.py",
    # The bounded opening of a skill or context body that a listing card peeks at.
    "app/core/excerpt.py",
    # The client that dials the address it validated, for a URL the party being
    # validated chose. A miss here is an SSRF the operator cannot see.
    "app/core/pinned_http.py",
    # Its first sync connector caller: a crawl whose every link is chosen by the
    # site it reads, and the page text that decides what a sync re-embeds (#984).
    "app/services/rag/connectors/web.py",
    "app/services/rag/web_page.py",
    # Which host is sent the Entra app's secret, its Graph token or neither.
    "app/services/rag/connectors/sharepoint.py",
    # The query strings a knowledge search runs, and the one place an expansion
    # failure is allowed to fall back to the plain query (#1649).
    "app/services/rag/query_analysis.py",
    # The one size limit that runs before a body is read. Every other one in this
    # codebase measures bytes that have already arrived.
    "app/core/body_limit.py",
    # The worker's judgement of its own event loop. It decides to `SIGKILL` the
    # process it runs in, on the two stacks that have no supervisor reading a
    # beat from outside - so a wrong verdict is a restart loop in production.
    "app/core/watchdog.py",
    # What a refusal is allowed to say about the field it refuses. One shape on
    # the wire and one place that builds it - the module reads `loc` and `msg`
    # and nothing else, which is what keeps a caller's own submission out of a
    # response body a fifth call site would otherwise hand it (#882).
    "app/core/field_errors.py",
    "app/core/phone.py",
    # Whose memory a row is - the three owner-key shapes, and the one place they
    # are built and read. A leaf module because everything from the models to the
    # capability needs it and every other home is an import cycle (#788).
    "app/core/memory_keys.py",
    # Which tables alembic owns and which the vector store makes at runtime. It is
    # two statements, and one of them decides whether `db-check` reports real drift.
    "app/db/vector_tables.py",
    # Which explicit `null` on an update may reach a row. It is one function and
    # it stands between a legal request and a 500 naming a database constraint
    # (#637), which is the kind of thing a branch that never runs is exactly how
    # it comes back.
    "app/db/updates.py",
    "app/services/access.py",
    # How often a stranger may reach a public surface. It was configured and
    # never applied for the whole life of the product (#39), which is the
    # argument for holding the replacement to the bar every other refusal is
    # held to rather than reporting it.
    "app/services/rate_limit.py",
    "app/services/agent_chat.py",
    "app/services/follow_ups.py",
    "app/services/mentions.py",
    "app/services/agent_environment.py",
    "app/services/agent_exposure.py",
    # What the model is actually told about each tool, read off the built
    # toolset rather than the catalog's hand-typed one-liner - the Toolbox
    # panel and the exposure form's channel-lookups list both read it (#1473).
    "app/services/capability_contracts.py",
    "app/services/agent_trigger.py",
    "app/services/trigger_events.py",
    "app/services/trigger_dedupe.py",
    "app/services/trigger_templates.py",
    "app/schemas/trigger_template.py",
    "app/services/agent_registry.py",
    "app/services/agent_runner.py",
    "app/services/agent_templates.py",
    "app/services/agent_defaults.py",
    "app/services/shipped_agents.py",
    "app/services/agent_session.py",
    # The turn loop itself, shared by the dashboard chat and the embedded
    # widget since #634. One frame vocabulary for both sockets, and the sink a
    # public surface filters at - so a toggle that fails open sends an agent's
    # reasoning to a stranger.
    "app/services/run_stream.py",
    "app/services/run_reaper.py",
    "app/services/run_heartbeat.py",
    "app/repositories/agent_workspace.py",
    "app/repositories/local_service.py",
    "app/repositories/sandbox_connection.py",
    "app/repositories/skill_proposal.py",
    "app/services/attachments.py",
    # The one subprocess in the chat attachment feature (#1591): a managed,
    # killable `soffice` for DOC. Every branch is a refusal or a teardown - a
    # timeout, a cancellation, a nonzero exit, an oversized output - so it is held
    # to the same bar as the other security-sensitive services here.
    "app/services/office_convert.py",
    "app/services/chat_timeline.py",
    "app/services/local_service.py",
    "app/services/sandbox_connection.py",
    "app/services/sandbox_runtimes.py",
    "app/services/sandbox_workspace.py",
    "app/services/sandboxd_compat.py",
    # A `.zip` or `.tar.gz` unpacked into a workspace, and a folder zipped out
    # (#2479). Every refusal here - a `../` member, a link, a device, a bomb - is
    # what stops an upload from writing outside its folder.
    "app/services/workspace_archive.py",
    "app/services/usage_report.py",
    "app/services/skill_proposal.py",
    "app/services/skill_workspace.py",
    "app/services/channels/attachments.py",
    "app/services/channels/sms_media.py",
    "app/services/channels/dedupe.py",
    "app/services/channels/stream_lease.py",
    "app/services/channels/feedback.py",
    "app/services/channels/membership.py",
    "app/services/channels/mentions.py",
    # Approvals and questions offered as buttons in a chat, and the Slack app
    # around the bot (#2064, #2067, #2068).
    "app/services/channels/prompts.py",
    # Next steps offered as buttons, a press of which is the next message (#2301).
    "app/services/channels/next_steps.py",
    "app/services/channels/notification_send.py",

    "app/services/channels/handback.py",
    "app/services/channels/continuation.py",
    "app/services/parked_runs.py",
    "app/services/channels/slack_answer.py",
    "app/services/channels/slack_app.py",
    "app/services/channels/slack_manifest.py",
    "app/services/channels/telegram_format.py",
    "app/services/channels/telegram_press.py",
    # What the Discord adapter says and reads, without the I/O (#2079).
    "app/services/channels/discord_protocol.py",
    "app/repositories/channel_prompt.py",
    "app/services/collection_access.py",
    "app/repositories/collection_teardown.py",
    "app/repositories/pending_sandbox_destroy.py",
    "app/services/embed_session.py",
    "app/services/embed_voice_js.py",
    "app/services/embedding_resolution.py",
    "app/services/approvals.py",
    "app/services/audit.py",
    "app/services/impersonation.py",
    "app/services/ws_auth.py",
    "app/services/health.py",
    # The job queue that replaced Prefect (#2219). The claim spends an attempt
    # before the job runs and the worker never hot-loops, so a job that kills
    # its process cannot loop and a database that is down cannot spin it.
    "app/worker/*.py",
    "app/worker/tasks/background_tasks.py",
    "app/repositories/background_job.py",
    "app/repositories/background_schedule.py",
    "app/repositories/background_task_kind.py",
    "app/repositories/background_worker.py",
    "app/services/background_jobs.py",
    "app/services/api_runs.py",
    "app/api/routes/v1/admin_background_jobs.py",
    "app/api/routes/v1/run_requests.py",
    "app/services/email/templates.py",
    "app/services/ingestion_config.py",
    "app/services/rag/failures.py",
    "app/services/rag_scratch.py",
    # Who hears that a run stopped on its budget or parked on an approval. The
    # emails are the only signal for a run nobody was watching — a Slack
    # mention, a schedule — so a silent failure here is indistinguishable from
    # an agent that simply had nothing to say.
    "app/services/notifications.py",
    "app/services/notification_catalog.py",
    "app/services/notification_center.py",
    "app/services/notification_delivery.py",
    "app/services/notification_routes.py",
    "app/repositories/notification_route.py",
    "app/schemas/notification_route.py",
    "app/repositories/notification.py",
    "app/api/routes/v1/notifications.py",
    # The one action in this feature no organization-scoped role can reach.
    "app/services/announcement.py",
    "app/repositories/announcement.py",
    "app/api/routes/v1/admin_announcements.py",
    "app/services/mcp_catalog.py",
    "app/services/portal_catalog.py",
    "app/services/portals/__init__.py",
    "app/services/portals/base.py",
    "app/services/portals/exceptions.py",
    "app/services/portals/github.py",
    "app/services/portals/github_app.py",
    "app/services/portals/_api.py",
    "app/services/portals/_calendar.py",
    "app/services/portals/_files.py",
    "app/services/portals/github_oauth.py",
    "app/services/portals/google_calendar.py",
    "app/services/portals/google_drive.py",
    "app/services/portals/mail_auth.py",
    "app/services/portals/reply_metadata.py",
    "app/services/reply_expectation.py",
    "app/services/portals/mail_thread.py",
    "app/services/portals/microsoft_files.py",
    "app/services/portals/outlook_calendar.py",
    "app/services/portals/registry.py",
    "app/services/mcp_connection.py",
    "app/services/mirage_mounts.py",
    # Which providers can draw an image, derived from the SDK. What it decides is
    # what the Builder offers, and a probe that quietly starts answering "no"
    # would empty that picker without a single test failing.
    "app/services/decision_models.py",
    "app/services/image_models.py",
    "app/services/model_profile.py",
    # Whether an agent can run on a model. A classification that quietly widens
    # hides chat models from every picker; one that narrows lets a speech model
    # through publish (#2487).
    "app/services/model_outputs.py",
    "app/services/organization_secret.py",
    "app/services/openai_codex.py",
    # Same shape and the same reason as `image_models`: what a picker offers is
    # decided by filtering a data file against what the platform can actually
    # reach, and an entry that quietly stops being offered - or starts being
    # offered and fails on the first voice note - is invisible without a test.
    "app/services/mcp_listing.py",
    "app/services/mcp_registry.py",
    "app/services/mcp_audit.py",
    "app/services/speech_to_text.py",
    "app/services/speech_to_text_settings.py",
    "app/services/realtime_models.py",
    "app/services/provider_keys.py",
    "app/repositories/voice_call.py",
    "app/services/voice/**",
    "app/api/routes/v1/voice.py",
    "app/worker/tasks/voice_tasks.py",
    "app/db/models/voice_call.py",
    "app/schemas/voice.py",
    "app/services/transcription.py",
    "app/services/text_to_speech_models.py",
    "app/services/text_to_speech/__init__.py",
    "app/services/text_to_speech/clients.py",
    "app/services/text_to_speech/exceptions.py",
    "app/services/text_to_speech/synthesis.py",
    "app/services/video_models.py",
    "app/services/video_generation/__init__.py",
    "app/services/video_generation/clients.py",
    "app/services/video_generation/exceptions.py",
    "app/services/video_generation/generation.py",
    "app/services/provider_error_code.py",
    "app/services/transcription_usage.py",
    # The rest of `app/services/rag/` came with the template and is reported
    # rather than gated. This module is ours and is only refusals - where a
    # remote file may be written, and what may reach a Drive query - so the
    # branch that is never taken in normal operation is the whole point of it.
    "app/services/rag/remote_names.py",
    # The Drive REST client that replaced Google's discovery SDK: written here,
    # so held to the bar.
    "app/services/rag/drive_api.py",
    # What a chunk is, and therefore what a search can find. Written here rather
    # than imported (#158), and the rest of the pipeline can only retrieve what
    # this module decided to store - so it is held to the bar the code it
    # replaced never was.
    "app/services/rag/_splitters.py",
    # The retrieval filter contract (FA-039): the narrowing-only business
    # filters, the server-trusted tenant/authorization scope, and their
    # composition. Trust-boundary validation, gated where the rest of the RAG
    # pipeline is only reported.
    "app/services/rag/filters.py",
    "app/services/rag/overlay.py",
    "app/services/rag/parent_context.py",
    # Whether a citation may name a page: a Markdown file was cited as "page 1" (#2489).
    "app/services/rag/citation.py",
    "app/services/sharing.py",
    "app/services/skills.py",
    "app/services/exporting.py",
    "app/services/run_export.py",
    "app/services/spend.py",
    "app/services/stats.py",
    "app/services/transcript.py",
    "app/db/models/agent_environment.py",
    "app/schemas/agent_environment.py",
    "app/repositories/agent_environment.py",
    "app/repositories/agent_exposure.py",
    "app/repositories/agent_trigger.py",
    "app/repositories/ingestion_spend.py",
    "app/repositories/mcp_connection.py",
    "app/repositories/mcp_server_check.py",
    "app/repositories/organization_secret.py",
    "app/repositories/_visibility.py",
    "app/repositories/resource_grant.py",
    "app/api/routes/v1/_sharing_routes.py",
    "app/api/routes/v1/_sharing_loaders.py",
    "app/api/routes/v1/secrets.py",
    "app/api/routes/v1/agent_environments.py",
    "app/api/routes/v1/agent_exposures.py",
    "app/api/routes/v1/agent_triggers.py",
    "app/api/routes/v1/trigger_webhooks.py",
    "app/api/routes/v1/local_services.py",
    "app/api/routes/v1/sandbox_connections.py",
    "app/api/routes/v1/skill_changes.py",
    "app/api/routes/v1/workspaces.py",
    "app/api/routes/v1/_workspace_bytes.py",
    "app/api/routes/v1/sharing.py",
    "app/api/routes/v1/stats.py",
    "app/repositories/run_manifest.py",
    "app/db/models/intercepted_tool_call.py",
    "app/repositories/intercepted_tool_call.py",
    "app/repositories/dashboard_layout.py",
    "app/services/dashboard_layout.py",
    "app/schemas/onboarding.py",
    "app/repositories/onboarding.py",
    "app/services/onboarding.py",
    "app/api/routes/v1/me_onboarding.py",
    "app/api/routes/v1/me_dashboard_layout.py",
    "app/repositories/dashboard_preset.py",
    "app/services/dashboard_preset.py",
    "app/repositories/context.py",
    "app/services/context.py",
    "app/repositories/project.py",
    "app/services/project.py",
    "app/services/project_workspace_guard.py",
    "app/services/project_workspace_transfer.py",
    "app/worker/tasks/project_tasks.py",
    "app/api/routes/v1/projects.py",
    "app/api/routes/v1/context.py",
    "app/db/models/dataset.py",
    "app/schemas/dataset.py",
    "app/schemas/dataset_helpers.py",
    "app/repositories/dataset.py",
    "app/repositories/_visibility.py",
    "app/services/dataset.py",
    "app/services/dataset_files.py",
    "app/api/routes/v1/datasets.py",
    "app/db/models/knowledge_gap.py",
    "app/schemas/knowledge_gap.py",
    "app/repositories/knowledge_gap.py",
    "app/services/knowledge_gaps.py",
    "app/api/routes/v1/knowledge_gaps.py",
    "app/services/system_agents.py",
    "app/services/system_agent_runner.py",
    "app/db/models/case_score.py",
    "app/schemas/scoring.py",
    "app/repositories/case_score.py",
    "app/services/scorers.py",
    "app/services/scoring.py",
    "app/db/models/case_generation.py",
    "app/schemas/case_generation.py",
    "app/repositories/case_generation.py",
    "app/services/case_generation/__init__.py",
    "app/services/case_generation/facade.py",
    "app/services/case_generation/sources.py",
    "app/services/case_generation/drafts.py",
    "app/services/case_generation/prompt.py",
    "app/services/case_generation/estimate.py",
    "app/services/case_generation/generation.py",
    "app/services/case_generation/worker.py",
    "app/api/routes/v1/dataset_generations.py",
    "app/schemas/agent_test_set.py",
    "app/services/agent_test_set.py",
    "app/api/routes/v1/agent_test_sets.py",
    "app/db/models/artifact.py",
    "app/db/models/artifact_share_request.py",
    "app/schemas/artifact.py",
    "app/schemas/artifact_share_request.py",
    "app/repositories/artifact.py",
    "app/repositories/artifact_share_request.py",
    "app/services/artifact.py",
    "app/services/artifact_actions.py",
    "app/services/artifact_bindings.py",
    "app/services/artifact_log_bindings.py",
    "app/services/artifact_consent.py",
    "app/services/artifact_diff.py",
    "app/services/artifact_lint.py",
    "app/services/artifact_ownership.py",
    "app/services/artifact_public_data.py",
    "app/services/artifact_request_service.py",
    "app/services/artifact_requests.py",
    "app/services/artifact_render.py",
    "app/api/routes/v1/_artifact_bytes.py",
    "app/api/routes/v1/artifacts.py",
    "app/db/models/visual.py",
    "app/db/models/pending_sandbox_destroy.py",
    "app/schemas/visual.py",
    "app/repositories/visual.py",
    "app/services/visual.py",
    "app/services/visual_render.py",
    "app/core/map_tiles.py",
    "app/services/tile_proxy.py",
    "app/api/routes/v1/visuals.py",
    "app/db/models/api_key.py",
    "app/schemas/api_key.py",
    "app/repositories/api_key.py",
    "app/services/api_key.py",
    "app/api/routes/v1/api_keys.py",
    "app/db/models/oauth.py",
    "app/schemas/oauth_server.py",
    "app/repositories/oauth.py",
    "app/services/oauth_server.py",
    # A phone signed in from a QR the console shows: single-use, a minute, and the
    # session opened only when the phone redeems it (#2097).
    "app/services/device_pairing.py",
    # Web Push to the browsers a person subscribed: the deployment's own signing
    # key, made on first use, and a push channel through the delivery outbox.
    "app/services/web_push.py",
    "app/repositories/push_subscription.py",
    "app/schemas/push.py",
    "app/api/routes/v1/push.py",
    "app/services/apns.py",
    "app/services/push.py",
    "app/repositories/apns_device.py",
    "app/api/routes/v1/mcp_oauth.py",
    "app/api/public_api.py",
    "app/api/routes/v1/public_openapi.py",
    "app/services/platform_mcp/__init__.py",
    "app/services/platform_mcp/_api.py",
    "app/services/platform_mcp/_oauth.py",
    "app/services/platform_mcp/_tools.py",
    "app/schemas/change_event.py",
    "app/services/change_feed.py",
    "app/api/routes/v1/change_events.py",
    "app/services/device_hub/__init__.py",
    "app/services/device_hub/_hub.py",
    "app/services/device_hub/_protocol.py",
    "app/services/device_hub/workspace.py",
    "app/services/device_hub/services.py",
    "app/services/device_hub/local_context.py",
    "app/services/device_hub/exceptions.py",
    "app/api/routes/v1/device_hub.py",
    "app/db/models/organization_assistant.py",
    "app/repositories/organization_assistant.py",
    "app/schemas/assistant.py",
    "app/services/assistant.py",
    "app/api/routes/v1/assistant.py",
    "app/schemas/resource_usage.py",
    "app/services/resource_usage.py",
    "app/services/knowledge_reach.py",
    "app/repositories/memory.py",
    "app/repositories/memory_claim.py",
    "app/repositories/system_agent.py",
    "app/repositories/outcome.py",
    "app/services/memory/_native.py",
    "app/services/memory/_claims.py",
    "app/services/memory/_runtime.py",
    "app/services/memory/extraction.py",
    "app/services/memory/facade.py",
    "app/services/primary_conversation.py",
    "app/services/outcome.py",
    "app/services/system_agent.py",
    "app/services/routine_authority.py",
    "app/services/routine_devices.py",
    "app/services/routine_health.py",
    "app/repositories/routine_health.py",
    "app/db/models/routine_health.py",
    "app/services/imessage_webhook.py",
    "app/api/routes/v1/imessage_webhook.py",
    "app/repositories/routine_device_wait.py",
    "app/db/models/routine_device_wait.py",
    "app/schemas/local_target.py",
    "app/agents/routine_authority.py",
    "app/agents/routine_actions.py",
    "app/db/models/task_board.py",
    "app/repositories/task.py",
    "app/schemas/task.py",
    "app/services/tasks.py",
    "app/services/task_access.py",
    "app/services/task_columns.py",
    "app/services/task_execution.py",
    "app/api/routes/v1/tasks.py",
    "app/worker/tasks/task_tasks.py",
    "app/services/project_tracking.py",
    "app/schemas/project_tracking.py",
    "app/schemas/decision_episode.py",
    "app/schemas/telephony.py",
    "app/db/models/telephony.py",
    "app/repositories/telephony.py",
    "app/services/telephony/*.py",
    "app/agents/capabilities/phone_calls/*.py",
    "app/repositories/project_tracking.py",
    "app/db/models/project_tracking.py",
    "app/services/routine_sources.py",
    "app/schemas/routine_sources.py",
    "app/services/routine_actions.py",
    "app/schemas/routine_actions.py",
    "app/services/routine_delivery.py",
    "app/repositories/routine_delivery.py",
    "app/api/routes/v1/memory.py",
    "app/db/models/eval_run.py",
    "app/repositories/eval_run.py",
    "app/services/eval_comparison.py",
    "app/schemas/eval_threshold.py",
    "app/schemas/eval_run.py",
    "app/services/eval_run.py",
    "app/services/eval_run_worker.py",
    "app/api/routes/v1/eval_runs.py",
    "app/core/cron.py",
    "app/db/models/eval_suite.py",
    "app/repositories/eval_suite.py",
    "app/schemas/eval_suite.py",
    "app/services/eval_suite_threshold.py",
    "app/services/eval_suite.py",
    "app/api/routes/v1/eval_suites.py",
    "app/db/models/promotion_gate.py",
    "app/repositories/promotion_gate.py",
    "app/schemas/promotion_gate.py",
    "app/services/promotion_gate.py",
    # The authoring assistant: what it reads, what it proposes and how a person
    # decides. Its evidence reads are the boundary that keeps it blind to evals.
    "app/db/models/authoring.py",
    "app/repositories/authoring.py",
    "app/schemas/authoring.py",
    "app/services/authoring_evidence.py",
    "app/services/authoring_hunks.py",
    "app/services/authoring_fields.py",
    "app/services/authoring_config.py",
    "app/services/authoring.py",
    "app/services/authoring_worker.py",
    "app/api/routes/v1/authoring.py",
    "app/worker/tasks/authoring_tasks.py",
    "app/worker/tasks/case_generation_tasks.py",
    # Improvement sessions: the Improve pipeline run in the background up to a
    # checked proposal.
    "app/db/models/improvement.py",
    "app/repositories/improvement.py",
    "app/services/improvement_states.py",
    "app/services/improvement_data.py",
    "app/schemas/improvement.py",
    "app/services/improvement_pipeline.py",
    "app/api/routes/v1/improvements.py",
    "app/worker/tasks/improvement_tasks.py",
    "app/db/models/context_proposal.py",
    "app/repositories/context_proposal.py",
    "app/schemas/context_proposal.py",
    "app/services/context_proposal.py",
    "app/services/proposal_reach.py",
    "app/api/routes/v1/context_changes.py",
    "app/api/routes/v1/primary_conversations.py",
    "app/api/routes/v1/outcomes.py",
    "app/api/routes/v1/system_agents.py",
    "app/worker/tasks/memory_tasks.py",
    # Which past conversations an agent may search, and the full-text query over
    # them. The predicate is the whole of it - a way in added here is a way in for
    # every agent at once - so it is held to the same bar as the permission code
    # (#789).
    "app/repositories/conversation_search.py",
    "app/services/conversation_search.py",
    "app/db/models/conversation_task.py",
    "app/schemas/conversation_task.py",
    "app/repositories/conversation_task.py",
    "app/services/conversation_tasks.py",
    # What a coding-agent task changed, read under the conversation's own reader
    # check (#2480).
    "app/db/models/coding_task_change.py",
    "app/schemas/coding_task_change.py",
    "app/repositories/coding_task_change.py",
    "app/services/coding_task_changes.py",
    "app/db/models/coding_account.py",
    "app/schemas/coding_account.py",
    "app/repositories/coding_account.py",
    "app/services/coding_accounts.py",
    "app/services/personal_codex.py",
    "app/services/coding_account_lease.py",
    "app/services/personal_coding_snapshot.py",
    "app/services/personal_coding.py",
    "app/services/conversation_turns.py",
    "app/worker/tasks/conversation_tasks.py",
    "app/db/models/generated_media.py",
    "app/repositories/generated_media.py",
    "app/services/generated_media.py",
    "app/api/routes/v1/generated_media.py",
    "app/services/embed_generated_media.py",
    "app/api/routes/v1/embed_generated_media.py",
    "app/worker/tasks/generated_media_tasks.py",
    # What this deployment calls itself, who may register, and whether it is open
    # at all. The identity half is cosmetic; the other two are refusals - a sign-up
    # policy applied on the only unauthenticated paths that mint an account, and a
    # gate that decides whether the API answers at all - so a branch that never
    # runs in normal operation is the whole point of them.
    "app/db/models/deployment_settings.py",
    "app/core/update_gate.py",
    "app/schemas/updates.py",
    "app/services/updates.py",
    "app/repositories/diagnostics.py",
    "app/schemas/diagnostics.py",
    "app/services/diagnostics/*",
    "app/worker/tasks/diagnostics_tasks.py",
    "app/repositories/deployment_settings.py",
    "app/schemas/deployment_settings.py",
    "app/services/deployment_settings.py",
    "app/services/signup_policy.py",
    "app/core/maintenance.py",
    "app/api/routes/v1/branding.py",
    "app/api/routes/v1/_branding_bytes.py",
    "app/api/routes/v1/_chat_file_bytes.py",
    "app/api/routes/v1/admin_deployment_settings.py",
    "app/db/locks.py",
    "app/services/invitation_admission.py",
    # One unguarded attribute in a dependency made every wrong-method request a 500
    # on every route. Gated because it is a refusal path nobody exercises on purpose
    # and because it goes away the moment upstream guards it.
    "app/core/otel_compat.py",
    # Whether a running deployment matches a security profile. A control that
    # passed on a deployment that does not satisfy it is a sheet somebody puts in
    # front of an auditor, so every branch of every control is held to the gate
    # (#1448).
    "app/services/deployment_profile.py",
    # How long each class of data lives here, who may say so, and the sweep that
    # applies it. A number that resolves wrongly either deletes what a deployment
    # is obliged to keep or keeps what it is obliged to delete, and the sweep is
    # the one code path in the platform whose whole purpose is a hard delete - so
    # every branch of the precedence, every class, and every refusal is held to
    # the gate (#1420).
    "app/core/retention.py",
    "app/repositories/retention.py",
    "app/services/retention.py",
    # Where `HTTPException` joins the one error envelope. It answers a 405, an
    # unmatched path, and twenty-two direct raises, none of which a happy path sees.
    "app/api/exception_handlers.py",
    # The standalone ML services (FA-069). Held here because the surface is one
    # another Urban Stack component calls with a key and no person watching: the
    # refusals are the contract, the tenant scoping on a call record is what keeps
    # one integrator's usage out of another's, and a branch nobody exercises on
    # purpose is exactly the one an unattended caller finds.
    "app/services/ml/catalog.py",
    "app/services/ml/pii.py",
    "app/services/ml/parsing.py",
    "app/services/ml/facade.py",
    "app/services/ml/audio_duration.py",
    "app/services/ml/pcm_wav.py",
    "app/db/models/ml_service_call.py",
    "app/repositories/ml_service_call.py",
    "app/schemas/ml.py",
    "app/api/routes/v1/ml.py",
    # Typed records shared by the console, agent tools, workflow nodes and the
    # public API. Every branch is a refusal - a stale revision, a reused
    # operation key, another tenant's table, an archived column - and a write path
    # that skipped one would overwrite or leak a tenant's rows silently (#1782).
    "app/db/models/virtual_table.py",
    "app/repositories/virtual_table.py",
    "app/schemas/virtual_table.py",
    "app/services/virtual_tables/**",
    "app/api/routes/v1/virtual_tables.py",
    "app/api/routes/v1/_table_responses.py",
    "app/api/routes/v1/_path_convertors.py",
    # Groups, directory group mappings, and signing in with a directory (#1773).
    # Authentication and membership: what they refuse is the feature.
    "app/db/models/group.py",
    "app/db/models/directory_mapping.py",
    "app/repositories/group.py",
    "app/repositories/directory_mapping.py",
    "app/schemas/group.py",
    "app/schemas/directory.py",
    "app/services/group.py",
    "app/services/group_sharing.py",
    "app/services/group_spend.py",
    "app/services/group_quality.py",
    "app/services/directory/**",
    "app/api/routes/v1/groups.py",
    "app/api/routes/v1/directory_mappings.py",
    "app/api/routes/v1/directory_auth.py",
    "app/api/routes/v1/_directory_handoff.py",
    # Discord server roles mapped to groups, and the sync that applies them (#2569).
    "app/db/models/discord_role_mapping.py",
    "app/repositories/discord_role_mapping.py",
    "app/schemas/discord_role_mapping.py",
    "app/services/discord_role_mapping.py",
    "app/services/channels/discord_roles.py",
    "app/worker/tasks/channel_tasks.py",
    # Node contracts, registry and graph validation (#1786). Mirrors the
    # capability registry's split: typed definitions in code, a graph checked
    # against them before it can be published.
    "app/db/models/workflow.py",
    "app/repositories/workflow.py",
    "app/schemas/workflow.py",
    "app/services/workflow_registry.py",
    "app/schemas/workflow_portable.py",
    "app/services/workflow_portable.py",
    "app/services/workflow_agents.py",
    "app/services/workflow_execution/agent_conversations.py",
    "app/workflows/**",
    "app/api/routes/v1/workflows.py",
    # Durable execution: run/node/attempt state machine, the dispatch outbox,
    # the reconciler and the event stream (#1788).
    "app/db/models/workflow_run.py",
    "app/repositories/workflow_run.py",
    "app/schemas/workflow_run.py",
    "app/services/workflow_execution/**",
    "app/worker/tasks/workflow_tasks.py",
    "app/api/routes/v1/workflow_runs.py",
    # What a workflow's real runs did over a window, for its runs page.
    "app/schemas/workflow_stats.py",
    "app/services/workflow_stats.py",
    # One text-similarity scorer: text.compare, approval edit ratios and evals.
    "app/services/text_similarity.py",
    # A model step's evals: its cases, and the test runs that tried them.
    "app/db/models/workflow_eval.py",
    "app/repositories/workflow_eval.py",
    "app/schemas/workflow_eval.py",
    "app/services/workflow_evals.py",
    "app/api/routes/v1/workflow_evals.py",
    # The ways in to a workflow (#1792): its webhooks and schedules, the
    # workflow-run WebSocket and the chat's door through it, and the publish
    # that switches its trigger node on.
    "app/db/models/workflow_exposure.py",
    "app/repositories/workflow_exposure.py",
    "app/schemas/workflow_exposure.py",
    "app/services/workflow_exposure.py",
    "app/services/workflow_webhook_test.py",
    "app/services/workflow_triggers.py",
    "app/services/workflow_authority.py",
    "app/services/workflow_run_socket.py",
    "app/api/routes/v1/workflow_exposures.py",
    "app/api/routes/v1/workflow_run_socket.py",
    # A run's files (#1791): what a `FileRef` resolves to, and the one lookup
    # every file step reads through.
    "app/db/models/workflow_file.py",
    "app/repositories/workflow_file.py",
    # What a `human.approval` step asks a person, and the queue deciding it.
    "app/db/models/workflow_approval.py",
    "app/repositories/workflow_approval.py",
    "app/schemas/workflow_approval.py",
    "app/api/routes/v1/workflow_approvals.py",
    # A workflow's own log: the lines a step's Quick Log writes as it settles.
    "app/db/models/workflow_log_entry.py",
    "app/repositories/workflow_log_entry.py",
    "app/schemas/workflow_log_entry.py",
    # Alert rules on that log, and the worker's count of each over its window.
    "app/db/models/workflow_log_alert.py",
    "app/repositories/workflow_log_alert.py",
    "app/schemas/workflow_log_alert.py",
    "app/services/workflow_log_alerts.py",
    "app/api/routes/v1/workflow_log_alerts.py",
    # A table's triggers (#1785): the workflows a record added to it starts,
    # and every refusal between the two - before activation, a filter miss, a
    # cycle, a revoked principal - is a run that must not start.
    "app/db/models/virtual_table_trigger.py",
    "app/repositories/virtual_table_trigger.py",
    "app/schemas/virtual_table_trigger.py",
    "app/api/routes/v1/virtual_table_triggers.py",
    # A saved table/kanban/list view (#1783): a console-only sub-resource of a
    # table, sharing its service package but with its own model, repo, schema and
    # route file. Every branch here is also a refusal - a private view another
    # member owns, a rename that collides, a non-owner without `tables:edit` `ALL`.
    "app/db/models/table_view.py",
    "app/repositories/table_view.py",
    "app/schemas/table_view.py",
    "app/api/routes/v1/table_views.py",
    "app/services/live_browse.py",
    "app/services/live_turn.py",
    "app/services/chat_room.py",
    "app/services/redis_view.py",
    "app/services/browse_plans.py",
    "app/api/routes/v1/browse_live.py",
    "app/db/models/browser_sign_in.py",
    "app/repositories/browser_sign_in.py",
    "app/schemas/browser_sign_in.py",
    "app/services/browser_sign_in.py",
    "app/services/browser_sign_in_session.py",
    "app/api/routes/v1/browser_sign_ins.py",
    # What the platform does for people on its own initiative, all of it in
    # background jobs, and the suggestions it leaves them (#2301).
    "app/services/proactive/**",
    "app/services/suggestions.py",
    "app/services/role_packs.py",
    "app/repositories/suggestion.py",
    "app/api/routes/v1/me_suggestions.py",
    "app/worker/tasks/proactive_tasks.py",
    # Watches over a person's own connections, and the presets they start from.
    "app/services/watches.py",
    "app/services/watch_sources.py",
    "app/services/routine_notifications.py",
    "app/schemas/routine_notifications.py",
    "app/schemas/watch_sources.py",
    "app/services/watch_presets.py",
    "app/repositories/watch.py",
    "app/api/routes/v1/me_watches.py",
]
# Mirrors `[tool.coverage.run] omit` - one definition of "ours", asserted equal
# by tests/test_coverage_gate.py.
exclude = [
    "app/agents/mcp_oauth.py",
]

[tool.ty.overrides.rules]
invalid-parameter-default = "error"
invalid-argument-type = "error"
invalid-assignment = "error"
unresolved-attribute = "error"
deprecated = "error"
no-matching-overload = "error"
unused-type-ignore-comment = "error"
unsupported-operator = "error"
invalid-await = "error"
invalid-context-manager = "error"
missing-argument = "error"
# An ignore comment that has stopped suppressing anything is the failure mode
# this whole section exists to prevent, one line wide. In our own code it is an
# error, so a fixed stub or a rewritten call forces the comment out with it.
unused-ignore-comment = "error"
# The global "ignore" is for the template's optional parsers (pymupdf, docx,
# faker), which are genuinely absent in some configurations. Nothing the
# platform layer imports is optional, so here a name that does not resolve is a
# typo or a dependency somebody dropped, and both should stop the build.
import-not-found = "error"

[tool.pytest.ini_options]
testpaths = ["tests"]
# `--strict-markers` turns a marker that is not registered below into an error
# rather than a warning, so a misspelled marker name fails the run instead of
# silently dropping the test from `-m security`.
addopts = "--strict-markers"
# Dump blocked thread stacks while a stalled CI worker can still report them.
faulthandler_timeout = 180
markers = [
    "security: a refusal or security-boundary test. Collected as a set so the security report can show the refusals rather than assert them (#1417) — tenant isolation, permission and grant refusals, a budget checked before the model call, an approval decided once, secret confidentiality, spec publish validation, channel sender identity, constant-time credential compare, webhook HMAC and rate limits. It is a label for the report, not a second gate: `make check` still runs everything.",
]



[tool.fastapi-fullstack]
generator_version = "0.2.17"
generated_at = "2026-07-26T18:01:04.489757+00:00"

[tool.coverage.run]
# `source` must be packages or directories — coverage silently ignores a file
# path here, which is how a whole module can sit in this list and never be
# measured. The platform layer is therefore selected with `include` globs,
# which do accept files, and CI asserts the measured set matches.
# `include` rather than `source`: `source` accepts only packages and
# directories, and silently ignores a file path — which is how a module can sit
# in this list and never be measured at all. `include` does accept files.
#
# The trade-off is that `include` only reports files that were imported, so a
# platform module nobody imports would drop out just as quietly. That is what
# tests/test_coverage_gate.py exists to catch.
include = [
    "app/agents/**",
    "app/commands/bootstrap.py",
    "app/commands/refresh_assistant.py",
    "app/commands/audit_skill_bindings.py",
    "app/commands/audit_verify.py",
    "app/commands/vault_rotate.py",
    "app/commands/sandbox_runtimes.py",
    "app/commands/invite_members.py",
    "app/commands/data_protection.py",
    "app/commands/mcp_audit.py",
    "app/core/permissions.py",
    "app/core/catalog/**",
    "app/core/secret_kinds.py",
    "app/core/tool_identity.py",
    "app/core/vault.py",
    # The audit trail, fail-closed and part of the caller's transaction. Every
    # service that records a privileged action is already in this list; the trail
    # they write to was not (#20).
    "app/core/audit.py",
    "app/core/background.py",
    # The dedicated bounded pool for blocking file work, kept off the executor
    # bcrypt and DNS share; its write is cancellation-safe (#1108).
    "app/core/blocking.py",
    # The LibreOffice conversion the RAG parser (and, later, chat) drives, owned
    # here so a hung `soffice` is killed on timeout rather than orphaned (#1685).
    "app/core/office_convert.py",
    # The SSRF allow/deny every outbound URL a tenant chooses is checked against -
    # a webhook, an MCP server address. A hole here is a request to an internal
    # address the operator cannot see, which is the pair to pinned_http below (#28).
    "app/core/sanitize.py",
    # The bounded opening of a skill or context body that a listing card peeks at.
    "app/core/excerpt.py",
    # The client that dials the address it validated, for a URL the party being
    # validated chose. A miss here is an SSRF the operator cannot see.
    "app/core/pinned_http.py",
    # Its first sync connector caller: a crawl whose every link is chosen by the
    # site it reads, and the page text that decides what a sync re-embeds (#984).
    "app/services/rag/connectors/web.py",
    "app/services/rag/web_page.py",
    # Which host is sent the Entra app's secret, its Graph token or neither.
    "app/services/rag/connectors/sharepoint.py",
    # The query strings a knowledge search runs, and the one place an expansion
    # failure is allowed to fall back to the plain query (#1649).
    "app/services/rag/query_analysis.py",
    # The one size limit that runs before a body is read. Every other one in this
    # codebase measures bytes that have already arrived.
    "app/core/body_limit.py",
    # The worker's judgement of its own event loop. It decides to `SIGKILL` the
    # process it runs in, on the two stacks that have no supervisor reading a
    # beat from outside - so a wrong verdict is a restart loop in production.
    "app/core/watchdog.py",
    # What a refusal is allowed to say about the field it refuses. One shape on
    # the wire and one place that builds it - the module reads `loc` and `msg`
    # and nothing else, which is what keeps a caller's own submission out of a
    # response body a fifth call site would otherwise hand it (#882).
    "app/core/field_errors.py",
    "app/core/phone.py",
    # Whose memory a row is - the three owner-key shapes, and the one place they
    # are built and read. A leaf module because everything from the models to the
    # capability needs it and every other home is an import cycle (#788).
    "app/core/memory_keys.py",
    # Which tables alembic owns and which the vector store makes at runtime. It is
    # two statements, and one of them decides whether `db-check` reports real drift.
    "app/db/vector_tables.py",
    # Which explicit `null` on an update may reach a row. It is one function and
    # it stands between a legal request and a 500 naming a database constraint
    # (#637), which is the kind of thing a branch that never runs is exactly how
    # it comes back.
    "app/db/updates.py",
    "app/services/access.py",
    # How often a stranger may reach a public surface. It was configured and
    # never applied for the whole life of the product (#39), which is the
    # argument for holding the replacement to the bar every other refusal is
    # held to rather than reporting it.
    "app/services/rate_limit.py",
    "app/services/agent_chat.py",
    "app/services/follow_ups.py",
    "app/services/mentions.py",
    "app/services/agent_environment.py",
    "app/services/agent_exposure.py",
    # What the model is actually told about each tool, read off the built
    # toolset rather than the catalog's hand-typed one-liner - the Toolbox
    # panel and the exposure form's channel-lookups list both read it (#1473).
    "app/services/capability_contracts.py",
    "app/services/agent_trigger.py",
    "app/services/trigger_events.py",
    "app/services/trigger_dedupe.py",
    "app/services/trigger_templates.py",
    "app/schemas/trigger_template.py",
    "app/services/agent_registry.py",
    "app/services/agent_runner.py",
    "app/services/agent_templates.py",
    "app/services/agent_defaults.py",
    "app/services/shipped_agents.py",
    # The whole dashboard chat wire format: every frame the WebSocket sends and
    # every frame it accepts. It sat outside both gates while `use-chat.ts`
    # switched on its names, which is the drift #144 describes.
    "app/services/agent_session.py",
    # The turn loop itself, shared by the dashboard chat and the embedded
    # widget since #634. One frame vocabulary for both sockets, and the sink a
    # public surface filters at - so a toggle that fails open sends an agent's
    # reasoning to a stranger.
    "app/services/run_stream.py",
    "app/services/run_reaper.py",
    "app/services/run_heartbeat.py",
    "app/repositories/agent_workspace.py",
    "app/repositories/local_service.py",
    "app/repositories/sandbox_connection.py",
    "app/repositories/skill_proposal.py",
    "app/services/attachments.py",
    # The one subprocess in the chat attachment feature (#1591): a managed,
    # killable `soffice` for DOC. Every branch is a refusal or a teardown - a
    # timeout, a cancellation, a nonzero exit, an oversized output - so it is held
    # to the same bar as the other security-sensitive services here.
    "app/services/office_convert.py",
    "app/services/chat_timeline.py",
    "app/services/local_service.py",
    "app/services/sandbox_connection.py",
    "app/services/sandbox_runtimes.py",
    "app/services/sandbox_workspace.py",
    "app/services/sandboxd_compat.py",
    # A `.zip` or `.tar.gz` unpacked into a workspace, and a folder zipped out
    # (#2479). Every refusal here - a `../` member, a link, a device, a bomb - is
    # what stops an upload from writing outside its folder.
    "app/services/workspace_archive.py",
    "app/services/usage_report.py",
    "app/services/skill_proposal.py",
    "app/services/skill_workspace.py",
    "app/services/channels/attachments.py",
    "app/services/channels/sms_media.py",
    "app/services/channels/dedupe.py",
    "app/services/channels/stream_lease.py",
    "app/services/channels/feedback.py",
    "app/services/channels/membership.py",
    "app/services/channels/mentions.py",
    # Approvals and questions offered as buttons in a chat, and the Slack app
    # around the bot (#2064, #2067, #2068).
    "app/services/channels/prompts.py",
    # Next steps offered as buttons, a press of which is the next message (#2301).
    "app/services/channels/next_steps.py",
    "app/services/channels/notification_send.py",

    "app/services/channels/handback.py",
    "app/services/channels/continuation.py",
    "app/services/parked_runs.py",
    "app/services/channels/slack_answer.py",
    "app/services/channels/slack_app.py",
    "app/services/channels/slack_manifest.py",
    "app/services/channels/telegram_format.py",
    "app/services/channels/telegram_press.py",
    # What the Discord adapter says and reads, without the I/O (#2079).
    "app/services/channels/discord_protocol.py",
    "app/repositories/channel_prompt.py",
    "app/services/collection_access.py",
    "app/repositories/collection_teardown.py",
    "app/repositories/pending_sandbox_destroy.py",
    # One visitor's turn on a public widget: who it runs as, how often they may
    # ask, and what the page is allowed to put in front of the model. An
    # unreachable `except BudgetExceeded` sat here for as long as it was
    # ungated, which is the failure this gate exists to name (#663).
    "app/services/embed_session.py",
    "app/services/embed_voice_js.py",
    "app/services/embedding_resolution.py",
    "app/services/approvals.py",
    # The read half of the audit trail. `audit:read` is the only way to see who
    # did what in an organization, and the scope is the whole point of the
    # service: one wrong argument here would show another tenant's actions.
    "app/services/audit.py",
    # An administrator acting as somebody else. Every branch here is a refusal -
    # an ended row, an expired one, a token carried onto another session - and a
    # refusal nobody has watched fail is the credential #1044 replaced.
    "app/services/impersonation.py",
    # The one check the chat socket re-runs on every frame - shared with the
    # handshake so an open socket is no more permissive than a new one (#1437).
    "app/services/ws_auth.py",
    # The health probes are ours: the template's version reported a hardcoded
    # "unknown" for the vector store and read environment variables for a
    # provider check. A probe nobody has watched fail is not a probe, which is
    # why this module is gated rather than reported.
    "app/services/health.py",
    # The job queue that replaced Prefect (#2219). The claim spends an attempt
    # before the job runs and the worker never hot-loops, so a job that kills
    # its process cannot loop and a database that is down cannot spin it.
    "app/worker/*.py",
    "app/worker/tasks/background_tasks.py",
    "app/repositories/background_job.py",
    "app/repositories/background_schedule.py",
    "app/repositories/background_task_kind.py",
    "app/repositories/background_worker.py",
    "app/services/background_jobs.py",
    "app/services/api_runs.py",
    "app/api/routes/v1/admin_background_jobs.py",
    "app/api/routes/v1/run_requests.py",
    # Template-inherited, gated anyway. Its fixed `emails/compiled` path pointed
    # at a directory that never existed, so every email raised
    # EmailTemplateError — and every caller logs and swallows that, so
    # registration returned 201 while sending nothing. A pure module whose
    # silent failure costs the product all of its mail is worth the bar; there
    # is nothing to mock to reach it.
    "app/services/email/templates.py",
    # How a collection parses, chunks and describes its documents. Template
    # code decides *how* to run a parser; this decides *which* one runs, for
    # whose collection, on whose model credential — which is platform layer,
    # and is the difference between a setting and a policy.
    "app/services/ingestion_config.py",
    # What a failed ingest is allowed to say. The rest of the RAG pipeline is
    # template-inherited and reported rather than gated, but this module decides
    # what leaves a worker for a stored column the whole organization reads —
    # the same judgement the vault and the exception handlers are held to.
    "app/services/rag/failures.py",
    "app/services/rag_scratch.py",
    # Who hears that a run stopped on its budget or parked on an approval. The
    # emails are the only signal for a run nobody was watching — a Slack
    # mention, a schedule — so a silent failure here is indistinguishable from
    # an agent that simply had nothing to say.
    "app/services/notifications.py",
    "app/services/notification_catalog.py",
    "app/services/notification_center.py",
    "app/services/notification_delivery.py",
    "app/services/notification_routes.py",
    "app/repositories/notification_route.py",
    "app/schemas/notification_route.py",
    "app/repositories/notification.py",
    "app/api/routes/v1/notifications.py",
    # The one action in this feature no organization-scoped role can reach.
    "app/services/announcement.py",
    "app/repositories/announcement.py",
    "app/api/routes/v1/admin_announcements.py",
    "app/services/mcp_catalog.py",
    "app/services/portal_catalog.py",
    "app/services/portals/__init__.py",
    "app/services/portals/base.py",
    "app/services/portals/exceptions.py",
    "app/services/portals/github.py",
    "app/services/portals/github_app.py",
    "app/services/portals/_api.py",
    "app/services/portals/_calendar.py",
    "app/services/portals/_files.py",
    "app/services/portals/github_oauth.py",
    "app/services/portals/google_calendar.py",
    "app/services/portals/google_drive.py",
    "app/services/portals/mail_auth.py",
    "app/services/portals/reply_metadata.py",
    "app/services/reply_expectation.py",
    "app/services/portals/mail_thread.py",
    "app/services/portals/microsoft_files.py",
    "app/services/portals/outlook_calendar.py",
    "app/services/portals/registry.py",
    "app/services/mcp_connection.py",
    "app/services/mirage_mounts.py",
    # Which providers can draw an image, derived from the SDK. What it decides is
    # what the Builder offers, and a probe that quietly starts answering "no"
    # would empty that picker without a single test failing.
    "app/services/decision_models.py",
    "app/services/image_models.py",
    "app/services/model_profile.py",
    # Whether an agent can run on a model. A classification that quietly widens
    # hides chat models from every picker; one that narrows lets a speech model
    # through publish (#2487).
    "app/services/model_outputs.py",
    "app/services/organization_secret.py",
    "app/services/openai_codex.py",
    # Same shape and the same reason as `image_models`: what a picker offers is
    # decided by filtering a data file against what the platform can actually
    # reach, and an entry that quietly stops being offered - or starts being
    # offered and fails on the first voice note - is invisible without a test.
    "app/services/mcp_listing.py",
    "app/services/mcp_registry.py",
    "app/services/mcp_audit.py",
    "app/services/speech_to_text.py",
    "app/services/speech_to_text_settings.py",
    "app/services/realtime_models.py",
    "app/services/provider_keys.py",
    "app/repositories/voice_call.py",
    "app/services/voice/**",
    "app/api/routes/v1/voice.py",
    "app/worker/tasks/voice_tasks.py",
    "app/db/models/voice_call.py",
    "app/schemas/voice.py",
    "app/services/transcription.py",
    "app/services/text_to_speech_models.py",
    "app/services/text_to_speech/__init__.py",
    "app/services/text_to_speech/clients.py",
    "app/services/text_to_speech/exceptions.py",
    "app/services/text_to_speech/synthesis.py",
    "app/services/video_models.py",
    "app/services/video_generation/__init__.py",
    "app/services/video_generation/clients.py",
    "app/services/video_generation/exceptions.py",
    "app/services/video_generation/generation.py",
    "app/services/provider_error_code.py",
    "app/services/transcription_usage.py",
    # The rest of `app/services/rag/` came with the template and is reported
    # rather than gated. This module is ours and is only refusals - where a
    # remote file may be written, and what may reach a Drive query - so the
    # branch that is never taken in normal operation is the whole point of it.
    "app/services/rag/remote_names.py",
    # The Drive REST client that replaced Google's discovery SDK: written here,
    # so held to the bar.
    "app/services/rag/drive_api.py",
    # What a chunk is, and therefore what a search can find. Written here rather
    # than imported (#158), and the rest of the pipeline can only retrieve what
    # this module decided to store - so it is held to the bar the code it
    # replaced never was.
    "app/services/rag/_splitters.py",
    # The retrieval filter contract (FA-039): the narrowing-only business
    # filters, the server-trusted tenant/authorization scope, and their
    # composition. Trust-boundary validation, gated where the rest of the RAG
    # pipeline is only reported.
    "app/services/rag/filters.py",
    "app/services/rag/overlay.py",
    "app/services/rag/parent_context.py",
    # Whether a citation may name a page: a Markdown file was cited as "page 1" (#2489).
    "app/services/rag/citation.py",
    "app/services/sharing.py",
    "app/services/skills.py",
    "app/services/exporting.py",
    "app/services/run_export.py",
    "app/services/spend.py",
    "app/services/stats.py",
    "app/services/transcript.py",
    "app/db/models/agent_environment.py",
    "app/schemas/agent_environment.py",
    "app/repositories/agent_environment.py",
    "app/repositories/agent_exposure.py",
    "app/repositories/agent_trigger.py",
    "app/repositories/ingestion_spend.py",
    "app/repositories/mcp_connection.py",
    "app/repositories/mcp_server_check.py",
    "app/repositories/organization_secret.py",
    "app/repositories/_visibility.py",
    "app/repositories/resource_grant.py",
    "app/api/routes/v1/_sharing_routes.py",
    "app/api/routes/v1/_sharing_loaders.py",
    "app/api/routes/v1/secrets.py",
    "app/api/routes/v1/agent_environments.py",
    "app/api/routes/v1/agent_exposures.py",
    "app/api/routes/v1/agent_triggers.py",
    "app/api/routes/v1/trigger_webhooks.py",
    "app/api/routes/v1/local_services.py",
    "app/api/routes/v1/sandbox_connections.py",
    "app/api/routes/v1/skill_changes.py",
    "app/api/routes/v1/workspaces.py",
    "app/api/routes/v1/_workspace_bytes.py",
    "app/api/routes/v1/sharing.py",
    "app/api/routes/v1/stats.py",
    "app/repositories/run_manifest.py",
    "app/db/models/intercepted_tool_call.py",
    "app/repositories/intercepted_tool_call.py",
    "app/repositories/dashboard_layout.py",
    "app/services/dashboard_layout.py",
    "app/schemas/onboarding.py",
    "app/repositories/onboarding.py",
    "app/services/onboarding.py",
    "app/api/routes/v1/me_onboarding.py",
    "app/api/routes/v1/me_dashboard_layout.py",
    "app/repositories/dashboard_preset.py",
    "app/services/dashboard_preset.py",
    "app/repositories/context.py",
    "app/services/context.py",
    "app/repositories/project.py",
    "app/services/project.py",
    "app/services/project_workspace_guard.py",
    "app/services/project_workspace_transfer.py",
    "app/worker/tasks/project_tasks.py",
    "app/api/routes/v1/projects.py",
    "app/api/routes/v1/context.py",
    "app/db/models/dataset.py",
    "app/schemas/dataset.py",
    "app/schemas/dataset_helpers.py",
    "app/repositories/dataset.py",
    "app/repositories/_visibility.py",
    "app/services/dataset.py",
    "app/services/dataset_files.py",
    "app/api/routes/v1/datasets.py",
    "app/db/models/knowledge_gap.py",
    "app/schemas/knowledge_gap.py",
    "app/repositories/knowledge_gap.py",
    "app/services/knowledge_gaps.py",
    "app/api/routes/v1/knowledge_gaps.py",
    "app/services/system_agents.py",
    "app/services/system_agent_runner.py",
    "app/db/models/case_score.py",
    "app/schemas/scoring.py",
    "app/repositories/case_score.py",
    "app/services/scorers.py",
    "app/services/scoring.py",
    "app/db/models/case_generation.py",
    "app/schemas/case_generation.py",
    "app/repositories/case_generation.py",
    "app/services/case_generation/__init__.py",
    "app/services/case_generation/facade.py",
    "app/services/case_generation/sources.py",
    "app/services/case_generation/drafts.py",
    "app/services/case_generation/prompt.py",
    "app/services/case_generation/estimate.py",
    "app/services/case_generation/generation.py",
    "app/services/case_generation/worker.py",
    "app/api/routes/v1/dataset_generations.py",
    "app/schemas/agent_test_set.py",
    "app/services/agent_test_set.py",
    "app/api/routes/v1/agent_test_sets.py",
    "app/db/models/artifact.py",
    "app/db/models/artifact_share_request.py",
    "app/schemas/artifact.py",
    "app/schemas/artifact_share_request.py",
    "app/repositories/artifact.py",
    "app/repositories/artifact_share_request.py",
    "app/services/artifact.py",
    "app/services/artifact_actions.py",
    "app/services/artifact_bindings.py",
    "app/services/artifact_log_bindings.py",
    "app/services/artifact_consent.py",
    "app/services/artifact_diff.py",
    "app/services/artifact_lint.py",
    "app/services/artifact_ownership.py",
    "app/services/artifact_public_data.py",
    "app/services/artifact_request_service.py",
    "app/services/artifact_requests.py",
    "app/services/artifact_render.py",
    "app/api/routes/v1/_artifact_bytes.py",
    "app/api/routes/v1/artifacts.py",
    "app/db/models/visual.py",
    "app/db/models/pending_sandbox_destroy.py",
    "app/schemas/visual.py",
    "app/repositories/visual.py",
    "app/services/visual.py",
    "app/services/visual_render.py",
    "app/core/map_tiles.py",
    "app/services/tile_proxy.py",
    "app/api/routes/v1/visuals.py",
    "app/db/models/api_key.py",
    "app/schemas/api_key.py",
    "app/repositories/api_key.py",
    "app/services/api_key.py",
    "app/api/routes/v1/api_keys.py",
    "app/db/models/oauth.py",
    "app/schemas/oauth_server.py",
    "app/repositories/oauth.py",
    "app/services/oauth_server.py",
    # A phone signed in from a QR the console shows: single-use, a minute, and the
    # session opened only when the phone redeems it (#2097).
    "app/services/device_pairing.py",
    # Web Push to the browsers a person subscribed: the deployment's own signing
    # key, made on first use, and a push channel through the delivery outbox.
    "app/services/web_push.py",
    "app/repositories/push_subscription.py",
    "app/schemas/push.py",
    "app/api/routes/v1/push.py",
    "app/services/apns.py",
    "app/services/push.py",
    "app/repositories/apns_device.py",
    "app/api/routes/v1/mcp_oauth.py",
    "app/api/public_api.py",
    "app/api/routes/v1/public_openapi.py",
    "app/services/platform_mcp/__init__.py",
    "app/services/platform_mcp/_api.py",
    "app/services/platform_mcp/_oauth.py",
    "app/services/platform_mcp/_tools.py",
    "app/schemas/change_event.py",
    "app/services/change_feed.py",
    "app/api/routes/v1/change_events.py",
    "app/services/device_hub/__init__.py",
    "app/services/device_hub/_hub.py",
    "app/services/device_hub/_protocol.py",
    "app/services/device_hub/workspace.py",
    "app/services/device_hub/services.py",
    "app/services/device_hub/local_context.py",
    "app/services/device_hub/exceptions.py",
    "app/api/routes/v1/device_hub.py",
    "app/db/models/organization_assistant.py",
    "app/repositories/organization_assistant.py",
    "app/schemas/assistant.py",
    "app/services/assistant.py",
    "app/api/routes/v1/assistant.py",
    "app/schemas/resource_usage.py",
    "app/services/resource_usage.py",
    "app/services/knowledge_reach.py",
    "app/repositories/memory.py",
    "app/repositories/memory_claim.py",
    "app/repositories/system_agent.py",
    "app/repositories/outcome.py",
    "app/services/memory/_native.py",
    "app/services/memory/_claims.py",
    "app/services/memory/_runtime.py",
    "app/services/memory/extraction.py",
    "app/services/memory/facade.py",
    "app/services/primary_conversation.py",
    "app/services/outcome.py",
    "app/services/system_agent.py",
    "app/services/routine_authority.py",
    "app/services/routine_devices.py",
    "app/services/routine_health.py",
    "app/repositories/routine_health.py",
    "app/db/models/routine_health.py",
    "app/services/imessage_webhook.py",
    "app/api/routes/v1/imessage_webhook.py",
    "app/repositories/routine_device_wait.py",
    "app/db/models/routine_device_wait.py",
    "app/schemas/local_target.py",
    "app/agents/routine_authority.py",
    "app/agents/routine_actions.py",
    "app/db/models/task_board.py",
    "app/repositories/task.py",
    "app/schemas/task.py",
    "app/services/tasks.py",
    "app/services/task_access.py",
    "app/services/task_columns.py",
    "app/services/task_execution.py",
    "app/api/routes/v1/tasks.py",
    "app/worker/tasks/task_tasks.py",
    "app/services/project_tracking.py",
    "app/schemas/project_tracking.py",
    "app/schemas/decision_episode.py",
    "app/schemas/telephony.py",
    "app/db/models/telephony.py",
    "app/repositories/telephony.py",
    "app/services/telephony/*.py",
    "app/agents/capabilities/phone_calls/*.py",
    "app/repositories/project_tracking.py",
    "app/db/models/project_tracking.py",
    "app/services/routine_sources.py",
    "app/schemas/routine_sources.py",
    "app/services/routine_actions.py",
    "app/schemas/routine_actions.py",
    "app/services/routine_delivery.py",
    "app/repositories/routine_delivery.py",
    "app/api/routes/v1/memory.py",
    "app/db/models/eval_run.py",
    "app/repositories/eval_run.py",
    "app/services/eval_comparison.py",
    "app/schemas/eval_threshold.py",
    "app/schemas/eval_run.py",
    "app/services/eval_run.py",
    "app/services/eval_run_worker.py",
    "app/api/routes/v1/eval_runs.py",
    "app/core/cron.py",
    "app/db/models/eval_suite.py",
    "app/repositories/eval_suite.py",
    "app/schemas/eval_suite.py",
    "app/services/eval_suite_threshold.py",
    "app/services/eval_suite.py",
    "app/api/routes/v1/eval_suites.py",
    "app/db/models/promotion_gate.py",
    "app/repositories/promotion_gate.py",
    "app/schemas/promotion_gate.py",
    "app/services/promotion_gate.py",
    # The authoring assistant: what it reads, what it proposes and how a person
    # decides. Its evidence reads are the boundary that keeps it blind to evals.
    "app/db/models/authoring.py",
    "app/repositories/authoring.py",
    "app/schemas/authoring.py",
    "app/services/authoring_evidence.py",
    "app/services/authoring_hunks.py",
    "app/services/authoring_fields.py",
    "app/services/authoring_config.py",
    "app/services/authoring.py",
    "app/services/authoring_worker.py",
    "app/api/routes/v1/authoring.py",
    "app/worker/tasks/authoring_tasks.py",
    "app/worker/tasks/case_generation_tasks.py",
    # Improvement sessions: the Improve pipeline run in the background up to a
    # checked proposal.
    "app/db/models/improvement.py",
    "app/repositories/improvement.py",
    "app/services/improvement_states.py",
    "app/services/improvement_data.py",
    "app/schemas/improvement.py",
    "app/services/improvement_pipeline.py",
    "app/api/routes/v1/improvements.py",
    "app/worker/tasks/improvement_tasks.py",
    "app/db/models/context_proposal.py",
    "app/repositories/context_proposal.py",
    "app/schemas/context_proposal.py",
    "app/services/context_proposal.py",
    "app/services/proposal_reach.py",
    "app/api/routes/v1/context_changes.py",
    "app/api/routes/v1/primary_conversations.py",
    "app/api/routes/v1/outcomes.py",
    "app/api/routes/v1/system_agents.py",
    "app/worker/tasks/memory_tasks.py",
    # Which past conversations an agent may search, and the full-text query over
    # them. The predicate is the whole of it - a way in added here is a way in for
    # every agent at once - so it is held to the same bar as the permission code
    # (#789).
    "app/repositories/conversation_search.py",
    "app/services/conversation_search.py",
    "app/db/models/conversation_task.py",
    "app/schemas/conversation_task.py",
    "app/repositories/conversation_task.py",
    "app/services/conversation_tasks.py",
    # What a coding-agent task changed, read under the conversation's own reader
    # check (#2480).
    "app/db/models/coding_task_change.py",
    "app/schemas/coding_task_change.py",
    "app/repositories/coding_task_change.py",
    "app/services/coding_task_changes.py",
    "app/db/models/coding_account.py",
    "app/schemas/coding_account.py",
    "app/repositories/coding_account.py",
    "app/services/coding_accounts.py",
    "app/services/personal_codex.py",
    "app/services/coding_account_lease.py",
    "app/services/personal_coding_snapshot.py",
    "app/services/personal_coding.py",
    "app/services/conversation_turns.py",
    "app/worker/tasks/conversation_tasks.py",
    "app/db/models/generated_media.py",
    "app/repositories/generated_media.py",
    "app/services/generated_media.py",
    "app/api/routes/v1/generated_media.py",
    "app/services/embed_generated_media.py",
    "app/api/routes/v1/embed_generated_media.py",
    "app/worker/tasks/generated_media_tasks.py",
    # What this deployment calls itself, who may register, and whether it is open
    # at all. The identity half is cosmetic; the other two are refusals - a sign-up
    # policy applied on the only unauthenticated paths that mint an account, and a
    # gate that decides whether the API answers at all - so a branch that never
    # runs in normal operation is the whole point of them.
    "app/db/models/deployment_settings.py",
    "app/core/update_gate.py",
    "app/schemas/updates.py",
    "app/services/updates.py",
    "app/repositories/diagnostics.py",
    "app/schemas/diagnostics.py",
    "app/services/diagnostics/*",
    "app/worker/tasks/diagnostics_tasks.py",
    "app/repositories/deployment_settings.py",
    "app/schemas/deployment_settings.py",
    "app/services/deployment_settings.py",
    "app/services/signup_policy.py",
    "app/core/maintenance.py",
    "app/api/routes/v1/branding.py",
    "app/api/routes/v1/_branding_bytes.py",
    "app/api/routes/v1/_chat_file_bytes.py",
    "app/api/routes/v1/admin_deployment_settings.py",
    "app/db/locks.py",
    "app/services/invitation_admission.py",
    # One unguarded attribute in a dependency made every wrong-method request a 500
    # on every route. Gated because it is a refusal path nobody exercises on purpose
    # and because it goes away the moment upstream guards it.
    "app/core/otel_compat.py",
    # Whether a running deployment matches a security profile. A control that
    # passed on a deployment that does not satisfy it is a sheet somebody puts in
    # front of an auditor, so every branch of every control is held to the gate
    # (#1448).
    "app/services/deployment_profile.py",
    # How long each class of data lives here, who may say so, and the sweep that
    # applies it. A number that resolves wrongly either deletes what a deployment
    # is obliged to keep or keeps what it is obliged to delete, and the sweep is
    # the one code path in the platform whose whole purpose is a hard delete - so
    # every branch of the precedence, every class, and every refusal is held to
    # the gate (#1420).
    "app/core/retention.py",
    "app/repositories/retention.py",
    "app/services/retention.py",
    # Where `HTTPException` joins the one error envelope. It answers a 405, an
    # unmatched path, and twenty-two direct raises, none of which a happy path sees.
    "app/api/exception_handlers.py",
    # The standalone ML services (FA-069). Held here because the surface is one
    # another Urban Stack component calls with a key and no person watching: the
    # refusals are the contract, the tenant scoping on a call record is what keeps
    # one integrator's usage out of another's, and a branch nobody exercises on
    # purpose is exactly the one an unattended caller finds.
    "app/services/ml/catalog.py",
    "app/services/ml/pii.py",
    "app/services/ml/parsing.py",
    "app/services/ml/facade.py",
    "app/services/ml/audio_duration.py",
    "app/services/ml/pcm_wav.py",
    "app/db/models/ml_service_call.py",
    "app/repositories/ml_service_call.py",
    "app/schemas/ml.py",
    "app/api/routes/v1/ml.py",
    # Typed records shared by the console, agent tools, workflow nodes and the
    # public API. Every branch is a refusal - a stale revision, a reused
    # operation key, another tenant's table, an archived column - and a write path
    # that skipped one would overwrite or leak a tenant's rows silently (#1782).
    "app/db/models/virtual_table.py",
    "app/repositories/virtual_table.py",
    "app/schemas/virtual_table.py",
    "app/services/virtual_tables/**",
    "app/api/routes/v1/virtual_tables.py",
    "app/api/routes/v1/_table_responses.py",
    "app/api/routes/v1/_path_convertors.py",
    # Groups, directory group mappings, and signing in with a directory (#1773).
    # Authentication and membership: what they refuse is the feature.
    "app/db/models/group.py",
    "app/db/models/directory_mapping.py",
    "app/repositories/group.py",
    "app/repositories/directory_mapping.py",
    "app/schemas/group.py",
    "app/schemas/directory.py",
    "app/services/group.py",
    "app/services/group_sharing.py",
    "app/services/group_spend.py",
    "app/services/group_quality.py",
    "app/services/directory/**",
    "app/api/routes/v1/groups.py",
    "app/api/routes/v1/directory_mappings.py",
    "app/api/routes/v1/directory_auth.py",
    "app/api/routes/v1/_directory_handoff.py",
    # Discord server roles mapped to groups, and the sync that applies them (#2569).
    "app/db/models/discord_role_mapping.py",
    "app/repositories/discord_role_mapping.py",
    "app/schemas/discord_role_mapping.py",
    "app/services/discord_role_mapping.py",
    "app/services/channels/discord_roles.py",
    "app/worker/tasks/channel_tasks.py",
    # Node contracts, registry and graph validation (#1786). Mirrors the
    # capability registry's split: typed definitions in code, a graph checked
    # against them before it can be published.
    "app/db/models/workflow.py",
    "app/repositories/workflow.py",
    "app/schemas/workflow.py",
    "app/services/workflow_registry.py",
    "app/schemas/workflow_portable.py",
    "app/services/workflow_portable.py",
    "app/services/workflow_agents.py",
    "app/services/workflow_execution/agent_conversations.py",
    "app/workflows/**",
    "app/api/routes/v1/workflows.py",
    # Durable execution: run/node/attempt state machine, the dispatch outbox,
    # the reconciler and the event stream (#1788).
    "app/db/models/workflow_run.py",
    "app/repositories/workflow_run.py",
    "app/schemas/workflow_run.py",
    "app/services/workflow_execution/**",
    "app/worker/tasks/workflow_tasks.py",
    "app/api/routes/v1/workflow_runs.py",
    # What a workflow's real runs did over a window, for its runs page.
    "app/schemas/workflow_stats.py",
    "app/services/workflow_stats.py",
    # One text-similarity scorer: text.compare, approval edit ratios and evals.
    "app/services/text_similarity.py",
    # A model step's evals: its cases, and the test runs that tried them.
    "app/db/models/workflow_eval.py",
    "app/repositories/workflow_eval.py",
    "app/schemas/workflow_eval.py",
    "app/services/workflow_evals.py",
    "app/api/routes/v1/workflow_evals.py",
    # The ways in to a workflow (#1792): its webhooks and schedules, the
    # workflow-run WebSocket and the chat's door through it, and the publish
    # that switches its trigger node on.
    "app/db/models/workflow_exposure.py",
    "app/repositories/workflow_exposure.py",
    "app/schemas/workflow_exposure.py",
    "app/services/workflow_exposure.py",
    "app/services/workflow_webhook_test.py",
    "app/services/workflow_triggers.py",
    "app/services/workflow_authority.py",
    "app/services/workflow_run_socket.py",
    "app/api/routes/v1/workflow_exposures.py",
    "app/api/routes/v1/workflow_run_socket.py",
    # A run's files (#1791): what a `FileRef` resolves to, and the one lookup
    # every file step reads through.
    "app/db/models/workflow_file.py",
    "app/repositories/workflow_file.py",
    # What a `human.approval` step asks a person, and the queue deciding it.
    "app/db/models/workflow_approval.py",
    "app/repositories/workflow_approval.py",
    "app/schemas/workflow_approval.py",
    "app/api/routes/v1/workflow_approvals.py",
    # A workflow's own log: the lines a step's Quick Log writes as it settles.
    "app/db/models/workflow_log_entry.py",
    "app/repositories/workflow_log_entry.py",
    "app/schemas/workflow_log_entry.py",
    # Alert rules on that log, and the worker's count of each over its window.
    "app/db/models/workflow_log_alert.py",
    "app/repositories/workflow_log_alert.py",
    "app/schemas/workflow_log_alert.py",
    "app/services/workflow_log_alerts.py",
    "app/api/routes/v1/workflow_log_alerts.py",
    # A table's triggers (#1785): the workflows a record added to it starts,
    # and every refusal between the two - before activation, a filter miss, a
    # cycle, a revoked principal - is a run that must not start.
    "app/db/models/virtual_table_trigger.py",
    "app/repositories/virtual_table_trigger.py",
    "app/schemas/virtual_table_trigger.py",
    "app/api/routes/v1/virtual_table_triggers.py",
    # A saved table/kanban/list view (#1783): a console-only sub-resource of a
    # table, sharing its service package but with its own model, repo, schema and
    # route file. Every branch here is also a refusal - a private view another
    # member owns, a rename that collides, a non-owner without `tables:edit` `ALL`.
    "app/db/models/table_view.py",
    "app/repositories/table_view.py",
    "app/schemas/table_view.py",
    "app/api/routes/v1/table_views.py",
    "app/services/live_browse.py",
    "app/services/live_turn.py",
    "app/services/chat_room.py",
    "app/services/redis_view.py",
    "app/services/browse_plans.py",
    "app/api/routes/v1/browse_live.py",
    "app/db/models/browser_sign_in.py",
    "app/repositories/browser_sign_in.py",
    "app/schemas/browser_sign_in.py",
    "app/services/browser_sign_in.py",
    "app/services/browser_sign_in_session.py",
    "app/api/routes/v1/browser_sign_ins.py",
    # What the platform does for people on its own initiative, all of it in
    # background jobs, and the suggestions it leaves them (#2301).
    "app/services/proactive/**",
    "app/services/suggestions.py",
    "app/services/role_packs.py",
    "app/repositories/suggestion.py",
    "app/api/routes/v1/me_suggestions.py",
    "app/worker/tasks/proactive_tasks.py",
    # Watches over a person's own connections, and the presets they start from.
    "app/services/watches.py",
    "app/services/watch_sources.py",
    "app/services/routine_notifications.py",
    "app/schemas/routine_notifications.py",
    "app/schemas/watch_sources.py",
    "app/services/watch_presets.py",
    "app/repositories/watch.py",
    "app/api/routes/v1/me_watches.py",
]
branch = true
omit = [
    # The template's OAuth client for MCP servers: discovery, dynamic client
    # registration, token exchange and refresh. Protocol plumbing we inherited
    # rather than designed - holding it to 100% would mean mock-heavy tests of
    # OAuth internals. It joins the gate when we take ownership of it; its
    # observable contract (tokens applied, refreshed, refused) is pinned in
    # tests/test_mcp_connections.py.
    "app/agents/mcp_oauth.py",
]

[tool.coverage.report]
fail_under = 100
show_missing = true
skip_covered = true
exclude_also = [
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
    "@overload",
]

[tool.uv]
# aiogram 3.31 caps pydantic below 2.14, and every pydantic 2.13 pins a
# pydantic-core with no CPython 3.15 wheel, so without this the backend cannot
# be installed on the Python it runs (#2229). aiogram raises that cap with each
# pydantic minor release rather than for a break; the Telegram channel's tests
# exercise it. Drop this once aiogram allows 2.14.
#
# xai-sdk 1.20 caps packaging below 26, and virtualenv (pre-commit's) fixes
# CVE-2026-113725 only from 21.14.7, which needs packaging 26.3 - so the cap
# held a known advisory in the lock. xai-sdk reads versions with packaging's
# stable API; the xAI realtime tests exercise it. Drop this once xai-sdk allows 26.
override-dependencies = ["pydantic[email]>=2.14.3", "packaging>=26.3"]