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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

## 1.4.1

### Changes
- **`provably-sdk` minimum raised to 0.3.3.**

## 1.4.0

### Changes
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "sourcerykit"
version = "1.4.0"
version = "1.4.1"
description = "Counterspell for hallucinating agents. Python SDK that breaks the illusion on every tool call, API response, and MCP handoff before bad outputs propagate."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.12"
Expand Down Expand Up @@ -47,7 +47,7 @@ dependencies = [
"requests >=2.34",
"jsonschema>=4.0",
"psycopg[binary]>=3.1",
"provably-sdk>=0.3.2,<1",
"provably-sdk>=0.3.3,<1",
"pydantic>=2.6",
"python-dotenv>=1.2",
"sqlalchemy>=2.0",
Expand Down Expand Up @@ -154,7 +154,7 @@ exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError", "\\.\\.\\."]
sourcerykit = "sourcerykit.cli.main:app"

[tool.bumpversion]
current_version = "1.4.0"
current_version = "1.4.1"
commit = false
tag = false
tag_name = "v{new_version}"
Expand Down
3 changes: 3 additions & 0 deletions src/sourcerykit/_provably.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class SourceryKitTokenStore:
"""The session in sourcerykit's global JSON, or in an embedding app's ``.env``."""

def load(self) -> OAuthTokens | None:
load_app_dir_config.cache_clear()
get_settings.cache_clear()
# Raises SourceryKitConfigError when sourcerykit is not set up, as before the SDK split.
settings = get_settings()
# sourcerykit only ever signs in as its own client, so its tokens are that client's.
Expand Down Expand Up @@ -72,6 +74,7 @@ def clear_refresh_token(self) -> None:
os.environ.pop("PROVABLY_REFRESH_TOKEN", None)
load_local_env.cache_clear()
else:
load_app_dir_config.cache_clear()
payload = load_app_dir_config()
payload.pop("refresh_token", None)
CONFIG_FILE.write_text(json.dumps(payload))
Expand Down
1 change: 0 additions & 1 deletion src/sourcerykit/ui/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ def launch(trace_id: str, host: str = "127.0.0.1", port: int = 8743) -> None:
url = f"http://{host}:{port}/?id={trace_id}"
print(f"Opening trace dashboard at {url}")

# ponytail: open browser after uvicorn binds; 0.5s is enough for a local server
threading.Timer(0.5, webbrowser.open, args=[url]).start()

import uvicorn
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/test_provably_glue.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for sourcerykit._provably — how sourcerykit plugs into the Provably SDK."""

import json
import os
from pathlib import Path
from unittest.mock import patch
Expand Down Expand Up @@ -93,6 +94,38 @@ def test_load_reads_the_session_from_settings(self, monkeypatch: pytest.MonkeyPa
get_settings.cache_clear()


class TestSessionSharedBetweenProcesses:
@pytest.fixture
def session_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
from sourcerykit import config

path = tmp_path / "config.json"
monkeypatch.setattr(config, "CONFIG_FILE", path)
monkeypatch.setattr(_provably, "CONFIG_FILE", path)
for name in ("SOURCERYKIT_TOKEN_STORE", "PROVABLY_ACCESS_TOKEN", "PROVABLY_REFRESH_TOKEN"):
monkeypatch.delenv(name, raising=False)
path.write_text(json.dumps({"token": "at", "refresh_token": "rt", "org_id": _ORG}))
return path

def test_load_sees_a_session_another_process_rotated(self, session_file: Path) -> None:
store = SourceryKitTokenStore()
assert store.load() == OAuthTokens(access_token="at", refresh_token="rt", client_id="sourcerykit-cli")

session_file.write_text(json.dumps({"token": "at2", "refresh_token": "rt2", "org_id": _ORG}))

assert store.load() == OAuthTokens(access_token="at2", refresh_token="rt2", client_id="sourcerykit-cli")

def test_clearing_the_refresh_token_keeps_another_process_s_newer_access_token(self, session_file: Path) -> None:
from sourcerykit.config import load_app_dir_config

load_app_dir_config() # this process's cached copy still holds "at"
session_file.write_text(json.dumps({"token": "at2", "refresh_token": "rt2", "org_id": _ORG}))

SourceryKitTokenStore().clear_refresh_token()

assert json.loads(session_file.read_text()) == {"token": "at2", "org_id": _ORG}


def test_consent_page_has_its_own_setting(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("SOURCERYKIT_CONSENT_URL", raising=False)
# The app URL is the base of query-record links, so it must not move the consent page.
Expand Down
10 changes: 5 additions & 5 deletions uv.lock

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

Loading