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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -e '.[test]'
python -m pip install -e '.[test,ai]'
- name: Verify dependencies
run: python -m pip check
- name: Run tests
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ It supports the system but does not decide which strategy should be live. Strate
## Quick start

```bash
python -m pip install -e .
python -m pip install -e '.[test,ai]'
python -m pytest -q
```

The default runtime install (`python -m pip install .`) does not include the AI
client. Install `.[ai]` only for an approved AI consumer; it adds the pinned,
standard-library-only AIAuditBridge SDK, not the gateway service. Installation
does not enable AI audits, configure credentials, or grant execution authority.
The full test suite uses the real installed SDK with synthetic HTTP responses;
it does not call a model or verify production authentication.

## Useful docs
- [`docs/plugin_lifecycle_policy.md`](docs/plugin_lifecycle_policy.md)
- [`docs/market-regime-control-plan.md`](docs/market-regime-control-plan.md)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies = [
]

[project.optional-dependencies]
ai = ["ai-gateway-client @ git+https://github.com/QuantStrategyLab/AIAuditBridge.git@65c8bcf432f578f657c12d5dab0e16ebec0bef5a"]
test = ["build>=1.2", "pytest>=8", "ruff==0.16.6"]

[project.scripts]
Expand Down
68 changes: 68 additions & 0 deletions tests/test_ai_audit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import sys
import types
import json
import io
import tomllib
from importlib.metadata import distribution
from pathlib import Path

import pytest

Expand Down Expand Up @@ -57,6 +61,70 @@ def _clear_ai_audit_env(monkeypatch) -> None:
monkeypatch.delenv(key, raising=False)


def test_optional_ai_extra_installs_the_declared_gateway_sdk() -> None:
from ai_gateway_client import AiGatewayClient, AiResult, GatewayConfig

project = tomllib.loads(Path("pyproject.toml").read_text())["project"]
requirement, = project["optional-dependencies"]["ai"]
assert requirement.startswith("ai-gateway-client @ git+")
assert not any(item.startswith("ai-gateway-client") for item in project["dependencies"])
source = json.loads(distribution("ai-gateway-client").read_text("direct_url.json"))
assert source["vcs_info"]["commit_id"] == requirement.rsplit("@", 1)[1]
assert AiGatewayClient.__module__ == "ai_gateway_client.gateway_client"
assert AiResult.__module__ == "ai_gateway_client.gateway_client"
assert GatewayConfig.__module__ == "ai_gateway_client.config"


@pytest.mark.parametrize("entry", [ai_audit.run_crisis_ai_audit, ai_audit.run_taco_ai_audit])
def test_installed_sdk_consumes_codex_job_without_feedback_or_api_fallback(monkeypatch, entry):
from ai_gateway_client import gateway_client

_clear_ai_audit_env(monkeypatch)
monkeypatch.setenv("CODEX_AUDIT_SERVICE_URL", "https://gateway.invalid")
monkeypatch.setenv("AI_GATEWAY_SOURCE_REPO", "QuantStrategyLab/UsEquitySnapshotPipelines")
monkeypatch.setattr(gateway_client, "_fetch_oidc_token", lambda _audience: "synthetic")
monkeypatch.setattr(gateway_client.time, "sleep", lambda _seconds: None)
calls = []
job_id = "synthetic-job-000000000000"

def urlopen(request, **_kwargs):
calls.append((request.get_method(), request.full_url))
if request.get_method() == "POST":
assert request.full_url == "https://gateway.invalid/v1/ai/execute/jobs"
payload = json.loads(request.data)
assert payload["mode"] == "review_only"
assert payload["model"] == "gpt-6-astra"
assert payload["source_repository"] == "QuantStrategyLab/UsEquitySnapshotPipelines"
assert "watch_only" in payload["prompt"]
response = {"status": "queued", "job_id": job_id}
else:
assert request.full_url == f"https://gateway.invalid/v1/ai/execute/jobs/{job_id}"
response = {"status": "succeeded", "output": json.dumps({
"verdict": "review", "confidence": 0.8, "summary": "synthetic research opinion",
"mode": "live", "execution_controls": {"broker_order_allowed": True},
})}
return io.BytesIO(json.dumps(response).encode())

monkeypatch.setattr(gateway_client.urllib.request, "urlopen", urlopen)
feedback = []
monkeypatch.setattr(ai_audit, "_report_shadow_disagreement", lambda **fields: feedback.append(fields))
deterministic = {"profile": "synthetic", "canonical_route": "no_action", "suggested_action": "watch_only"}
payload = entry(deterministic, enabled=True, codex_enabled=True, codex_model="gpt-6-astra")

assert payload["status"] == "advisory"
assert payload["summary"] == "synthetic research opinion"
assert payload["deterministic_route"] == "no_action"
assert payload["final_route_unchanged"] is True
assert payload["mode"] == "shadow_only"
assert payload["execution_controls"]["broker_order_allowed"] is False
assert payload["execution_controls"]["live_allocation_mutation_allowed"] is False
assert payload["execution_controls"]["allocation_recommendation_allowed"] is False
assert len(payload["attempts"]) == 1
assert [method for method, _url in calls] == ["POST", "GET"]
assert feedback == []
assert deterministic["canonical_route"] == "no_action"


def test_ai_audit_uses_generic_anthropic_api_key(monkeypatch) -> None:
_clear_ai_audit_env(monkeypatch)
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test")
Expand Down
55 changes: 32 additions & 23 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.