[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 `..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"]