Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
run: |
export AIRFLOW_HOME="$RUNNER_TEMP/airflow-home"
# Line and branch coverage of this job's integration module; fails under 80% (pyproject.toml).
uv pip install --python "$RUNNER_TEMP/extra/bin/python" coverage==7.16.2
uv pip install --python "$RUNNER_TEMP/extra/bin/python" coverage==7.17.0
"$RUNNER_TEMP/extra/bin/python" -m coverage run -m pytest $TESTS
"$RUNNER_TEMP/extra/bin/python" -m coverage report --include="$COVERS"
- name: Type-check against the real framework
Expand All @@ -139,9 +139,44 @@ jobs:
PYTHON_VERSION: ${{ matrix.python }}
TYPECHECK: ${{ matrix.typecheck }}
run: |
uv pip install --python "$RUNNER_TEMP/extra/bin/python" mypy==1.19.1
uv pip install --python "$RUNNER_TEMP/extra/bin/python" mypy==1.20.2
"$RUNNER_TEMP/extra/bin/python" -m mypy --python-version "$PYTHON_VERSION" --follow-imports=silent $TYPECHECK

docs:
# Every python block of the README and docs/frameworks/*.md runs against a local
# stand-in for the Runtime API (scripts/check_docs.py, scripts/docs_stub.py), with
# scripted models: no key, no network beyond the package index. "core" runs in the
# dev environment; each framework guide runs against its pinned versions
# (ci/requirements-docs-*.txt), with --strict so a missing framework fails.
name: Docs examples (${{ matrix.guide }})
runs-on: ${{ github.event.repository.private && fromJSON(vars.CI_RUNS_ON || '["self-hosted","linux","x64","vericto-spot"]') || fromJSON(vars.CI_RUNS_ON_PUBLIC || '"ubuntu-latest"') }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
guide: [core, langchain, llamaindex, django, crewai, openai-agents, pydantic-ai, vanna]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: "3.13"
- name: README, DB-API and SQLAlchemy guides (dev environment)
if: matrix.guide == 'core'
run: |
uv sync --locked
uv run python scripts/check_docs.py --strict README.md docs/frameworks/dbapi.md docs/frameworks/sqlalchemy.md
- name: Framework guide against its pinned versions
if: matrix.guide != 'core'
env:
GUIDE: ${{ matrix.guide }}
run: |
uv venv --python 3.13 "$RUNNER_TEMP/docs"
uv pip install --python "$RUNNER_TEMP/docs/bin/python" -e . -r "ci/requirements-docs-$GUIDE.txt"
"$RUNNER_TEMP/docs/bin/python" scripts/check_docs.py --strict "docs/frameworks/$GUIDE.md"

build:
name: Build sdist and wheel
runs-on: ${{ github.event.repository.private && fromJSON(vars.CI_RUNS_ON || '["self-hosted","linux","x64","vericto-spot"]') || fromJSON(vars.CI_RUNS_ON_PUBLIC || '"ubuntu-latest"') }}
Expand Down
150 changes: 150 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,153 @@ All notable changes to this package are documented here. The format follows

## [Unreleased]

## [0.5.0] - 2026-10-23

One fail-open contract for both SDKs (shared with vericto-node 0.4.0) and the
business policy context of the Runtime API.

### Breaking change

- **`Vericto.check()` follows the fail mode.** Under `fail_mode="open"` (or
`VERICTO_FAIL_MODE=open`), when Vericto cannot answer (network error, timeout,
HTTP 5xx, a response that is not JSON or has no decision, rate limit after the
retries, monthly quota), `check()` now returns an `UNEVALUATED` verdict whose
`message` is the reason, and logs a warning, instead of raising
`VerictoUnavailable`. The LangChain `VerictoCheckSQLTool` already answered
`UNEVALUATED`; it now does it through `check()`, as every wrapper of both SDKs
does. vericto-node 0.4.0 makes the same change (its `check()` threw there).
Unchanged: under `fail_mode="closed"` (the default) `check()`, `enforce()` and
every wrapper raise `VerictoUnavailable` (the safest contract: a returned
`BLOCKED` would read as a rule decision that never happened); `enforce()` under
`"open"` returns `UNEVALUATED` and emits `VerictoFailOpenWarning` as before;
`VerictoConfigError` (key, scope, dialect, request) is raised in both modes.
Migration, for code that caught the error under `"open"`:

```python
# 0.4.x
try:
verdict = guard.check(sql, "postgres")
except VerictoUnavailable as exc:
no_verdict(str(exc))
else:
use(verdict)

# 0.5.0 (fail_mode="open")
verdict = guard.check(sql, "postgres")
if verdict.decision is Decision.UNEVALUATED:
no_verdict(verdict.message)
else:
use(verdict)
```

Code that runs with `fail_mode="closed"` needs no change.

### Added

- **Business policy context** (Runtime API M2): `check`, `enforce`, `prepare` and
`enforce_all` take `context={"ticket": ..., "tenant": ...}` and `end_user={...}`
keyword arguments, sent as `context` and `end_user`; so does
`RuntimeApiEvaluator.evaluate`. The values are checked before anything is sent,
with the limits of the service (vericto-fmw `policyContextSchema`): `ticket` and
`tenant` 1 to 128 characters, `end_user` up to 16 attributes with identifier
keys (at most 63 characters) and values of 1 to 256 characters, no control
characters, no other `context` key; lengths are counted in UTF-16 code units, as
the server counts them. An invalid value raises `VerictoConfigError` in both
fail modes; the message names the field, never the value. They take effect when
business policies are enabled in the workspace; they carry the API key's trust.
The verdict cache keys on them. New module `vericto.policy_context`; new exports
`vericto.PolicyContext` and `vericto.EndUser`.
- `vericto.sqlalchemy.instrument`, the LangChain and LlamaIndex
`VerictoSQLDatabase` and the LangChain `VerictoCheckSQLTool` take `context=` and
`end_user=`, as a value or as a callable read for every statement (for example a
`contextvars.ContextVar`'s `get`). Fixed values are validated at construction. On
an engine already instrumented, `VerictoSQLDatabase(context=...)` raises
`VerictoConfigError` (give them to `instrument`) instead of dropping them.
- A custom evaluator receives `context=` / `end_user=` keyword arguments only when
the caller gives them; `evaluate(sql, dialect)` implementations keep working
(and raise `TypeError`, rather than drop them, if they are given).
- `scripts/docs_stub.py` validates and records `context` and `end_user` like the
service.
- Tests: the fail-mode contract over HTTP in both modes for a network error, a
timeout, a 5xx and a body that is not JSON (`tests/test_fail_mode_contract.py`);
`context` / `end_user` in the request body, the limits, the cache, against the
fake server and the docs stub (`tests/test_policy_context.py`); the LangChain and
LlamaIndex wrappers under both fail modes and with the context.

### Fixed

- `Vericto.check()` and the LangChain `VerictoCheckSQLTool` disagreed under
`fail_mode="open"`: the tool answered `UNEVALUATED` while `check()`, which it is
built on, raised. The tool now simply calls `check()`, so the two cannot drift
apart again (see "Breaking change").

### Changed

- README: the fail-mode contract for `check()`, `enforce()` and the wrappers, with
the 0.4 to 0.5 change; a **Business policy context** section; the Spanish
section. LangChain guide: the check tool under both fail modes and a `context` /
`end_user` example; LlamaIndex and SQLAlchemy guides: the new arguments. Every
example runs in the `Docs examples` jobs.
- Dependencies: compatible dev-lock refresh (importlib-metadata 10.1.0,
websockets 17.3, xxhash 4.0.2, zipp 4.1.2; python-dateutil stays on 2.9 rather
than the 3.0 major that Airflow's range now admits). CI pins: crewai 1.15.30 in
`ci/requirements-docs-crewai.txt`, mypy 1.20.2 in the integration jobs. Runtime
dependency unchanged (urllib3).

## [0.4.1] - 2026-10-23

Documentation release, with one small fix. No other behavior change.

### Fixed

- An empty or whitespace-only `VERICTO_FAIL_MODE` now counts as unset and means
the documented default, `closed`. 0.4.0 raised `VerictoConfigError` for
`VERICTO_FAIL_MODE=` (for example a CI variable that expands to nothing), as
vericto-node did before its 0.2.2; every other `VERICTO_*` variable already
fell back to its default. An invalid non-empty value still raises, and the
error now names `VERICTO_FAIL_MODE` when the value came from it.

### Added

- **Framework guides** in `docs/frameworks/`, each with a runnable example and
the versions it was tested with: LangChain (incl. LangChain 1.x
`create_agent`), LlamaIndex, SQLAlchemy and any DB-API driver (official
integrations), and Django, CrewAI, the OpenAI Agents SDK, Pydantic AI and
Vanna 2.x (patterns: these frameworks have no vericto module; the guide shows
where to call the guard).
- `scripts/docs_stub.py`, a local stand-in for the Runtime API with the
service's status codes and response bodies, and `scripts/check_docs.py`,
which runs every `python` block of the README and the guides against it. New
CI job `Docs examples (...)`: the core examples in the dev environment, each
framework guide against its pins in `ci/requirements-docs-*.txt`.

### Changed

- README: a **Core API** section (`check`, `enforce`, `prepare`, `enforce_all`,
`require_dialect`, the `Verdict` fields), **Timeouts and retries** (the
timeout is per attempt; the worst case before `VerictoUnavailable` is about
`(max_retries + 1) × timeout` plus backoff, ~15.6 s with the defaults), **Sync
and async** (the API is synchronous; use `asyncio.to_thread` in async code;
`instrument()` on an `AsyncEngine` blocks the loop while it checks), a
runnable fail-closed / fail-open example, and a **Frameworks** table that says
which integrations are official and which are patterns. Corrected: the CA
bundle is an argument of `RuntimeApiEvaluator`, not of `Vericto`. The Spanish
section is updated to match.
- Docstrings for the public API that had none (`Verdict` fields,
`Verdict.blocked`, `Vericto.clear_cache`, `RuntimeApiEvaluator.evaluate` and
`close`, the DB-API, sqlite3 and Snowflake wrappers' entry points, the
LangChain and LlamaIndex properties).
- The sdist ships `docs/` and `scripts/` and no longer lists a `README.es.md`
that does not exist.
- Dev lock (`uv.lock`) refreshed with compatible updates (SQLAlchemy 2.1.6,
langchain-core 1.7.0, coverage 7.17.0, ruff 0.17.2, among others). The tests
now run against snowflake-connector-python 5.1.0 on Python 3.11+ (within the
`snowflake` extra's `>=3.0`; 4.8.0 stays on 3.10, which 5.x does not support),
which also drops filelock 3.32.7 (CVE-2026-112907, -112913, -112940) from the
3.11+ environments; `vericto.snowflake` works unchanged with both (its import
of the connector's splitter only gained a type-checker annotation). CI pins
langchain-core 1.7.0, SQLAlchemy 2.1.6 and coverage 7.17.0.

## [0.4.0] - 2026-10-18

**Now open source under the Apache License 2.0.** From this version on,
Expand Down Expand Up @@ -251,6 +398,9 @@ the Vericto Runtime API (`POST /api/v1/proxy/query`).
- The Vericto engine does not evaluate the `sqlite` dialect yet: the SQLite
wrappers raise `VerictoUnsupportedDialect` until the server lists it.

[0.5.0]: https://github.com/vericto/vericto-python/releases/tag/v0.5.0
[0.4.1]: https://github.com/vericto/vericto-python/releases/tag/v0.4.1
[0.4.0]: https://github.com/vericto/vericto-python/releases/tag/v0.4.0
[0.3.2]: https://github.com/vericto/vericto-python/releases/tag/v0.3.2
[0.3.1]: https://github.com/vericto/vericto-python/releases/tag/v0.3.1
[0.3.0]: https://github.com/vericto/vericto-python/releases/tag/v0.3.0
Expand Down
11 changes: 8 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ the README, "Live tests").
uv run mypy
uv run coverage run -m pytest
uv run coverage report --omit="*/vericto/langchain.py,*/vericto/llamaindex.py,*/vericto/airflow/*"
uv run python scripts/check_docs.py --strict README.md docs/frameworks/dbapi.md docs/frameworks/sqlalchemy.md
uv build
```
A change to a framework guide: run it against its pins too (README,
"Documentation examples").
4. Open a PR using the template. Keep PRs focused and under ~500 lines.

### Coverage
Expand All @@ -76,7 +79,7 @@ gates its own module. To run one locally (LangChain here):

```bash
uv venv /tmp/vericto-lc && uv pip install --python /tmp/vericto-lc -e . -r ci/requirements-langchain.txt
uv pip install --python /tmp/vericto-lc coverage==7.16.2
uv pip install --python /tmp/vericto-lc coverage==7.17.0
/tmp/vericto-lc/bin/python -m coverage run -m pytest tests/test_langchain.py
/tmp/vericto-lc/bin/python -m coverage report --include=src/vericto/langchain.py
```
Expand Down Expand Up @@ -112,8 +115,10 @@ when they cut a release.

- ✅ Required checks pass: `Lint and type-check`, `Test (Python 3.10)`,
`Test (Python 3.12)`, `Test (Python 3.14)`, `Build sdist and wheel` and
`gitleaks`. The `Integration (...)` jobs run on every pull request too and
should be green.
`gitleaks`. The `Integration (...)` and `Docs examples (...)` jobs run on
every pull request too and should be green.
- ✅ Code in the README and `docs/frameworks/` runs (`scripts/check_docs.py`), or
is marked `<!-- docs-check: skip REASON -->` with a reason.
- ✅ Coverage stays at or above 80% in every job.
- ✅ New behavior has tests, including the failure path (`fail_mode`, API errors).
- ✅ `CHANGELOG.md` updated under `[Unreleased]` when a user can see the change.
Expand Down
Loading
Loading