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

All notable changes to **flycanon** are documented here.

## [26.10.3] - 2026-10-14

### Fixed

- Serialize Python SDK token expiry in JSON mode before HTTP dispatch. Timezone-aware datetime values now reach the mint endpoint, while omitted fields retain their existing behavior. UTC and offset cases cover request and response values; native local mint, list and revoke verify the bounded token lifecycle.

## [26.10.2] - 2026-10-14

### Dependencies
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ over whole documents with citations — all behind a single HTTP service.
[![agentic](https://img.shields.io/badge/genai-fireflyframework--agentic-purple)](https://github.com/fireflyframework/fireflyframework-agentic)
[![OpenAPI](https://img.shields.io/badge/api-openapi%203.1-green)](docs/api-reference.md)
[![vector store](https://img.shields.io/badge/vector--store-pgvector%20%7C%20qdrant%20%7C%20chroma-336791)](docs/architecture.md#retrieval-backend-pluggable-dense-store)
[![Version](https://img.shields.io/badge/version-26.10.2-green.svg)](#)
[![Version](https://img.shields.io/badge/version-26.10.3-green.svg)](#)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)

</div>
Expand Down
6 changes: 3 additions & 3 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ For a host installation using the released wheel, follow [Install the released s

```bash
docker pull ghcr.io/fireflyframework/flycanon:latest # latest main
docker pull ghcr.io/fireflyframework/flycanon:26.10.2 # specific release
docker pull ghcr.io/fireflyframework/flycanon:26.10.3 # specific release
docker pull ghcr.io/fireflyframework/flycanon:26.10 # latest 26.10.x
```

Expand Down Expand Up @@ -681,12 +681,12 @@ The recommended upgrade dance:

```bash
# 1. Pull the new image
docker pull ghcr.io/fireflyframework/flycanon:26.10.2
docker pull ghcr.io/fireflyframework/flycanon:26.10.3

# 2. Run migrations against the live database
docker run --rm \
--env-file .env \
ghcr.io/fireflyframework/flycanon:26.10.2 migrate
ghcr.io/fireflyframework/flycanon:26.10.3 migrate

# 3. Roll the API + worker deployments
docker compose --env-file .env up -d
Expand Down
14 changes: 7 additions & 7 deletions docs/released-server-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Use the released source archive for the operator configuration and migration fil

## Download and verify

Download `flycanon-26.10.2-py3-none-any.whl`, `flycanon-26.10.2.tar.gz`, `runtime-requirements-26.10.2.txt`, and `SHA256SUMS` from the [26.10.2 release](https://github.com/fireflyframework/flycanon/releases/tag/v26.10.2). Verify the downloaded files against their matching checksum entries before extracting or installing them.
Download `flycanon-26.10.3-py3-none-any.whl`, `flycanon-26.10.3.tar.gz`, `runtime-requirements-26.10.3.txt`, and `SHA256SUMS` from the [26.10.3 release](https://github.com/fireflyframework/flycanon/releases/tag/v26.10.3). Verify the downloaded files against their matching checksum entries before extracting or installing them.

```bash
tar -xzf flycanon-26.10.2.tar.gz
cd flycanon-26.10.2
tar -xzf flycanon-26.10.3.tar.gz
cd flycanon-26.10.3
uv venv --python 3.13 .venv
uv pip install --python .venv/bin/python -r ../runtime-requirements-26.10.2.txt
uv pip install --python .venv/bin/python -r ../runtime-requirements-26.10.3.txt
```

The requirements file selects the released server wheel and these canonical framework references:
Expand All @@ -26,7 +26,7 @@ The equivalent installation without a requirements file is:

```bash
uv pip install --python .venv/bin/python \
'flycanon @ https://github.com/fireflyframework/flycanon/releases/download/v26.10.2/flycanon-26.10.2-py3-none-any.whl' \
'flycanon @ https://github.com/fireflyframework/flycanon/releases/download/v26.10.3/flycanon-26.10.3-py3-none-any.whl' \
'pyfly @ git+https://github.com/fireflyframework/fireflyframework-pyfly.git@v26.09.04' \
'fireflyframework-agentic @ https://github.com/fireflyframework/fireflyframework-agentic/releases/download/v26.10.1/fireflyframework_agentic-26.10.1-py3-none-any.whl'
```
Expand All @@ -39,11 +39,11 @@ This release retains pyfly `v26.09.04` and the existing PostgreSQL EDA outbox. A

## Configure the runtime

Keep `pyfly.yaml`, `alembic.ini`, `migrations/`, and `env_template` from the source archive in the working directory. The wheel does not supply the root-level `pyfly.yaml`. The release also provides a byte-identical `pyfly-26.10.2.yaml` copy for operators who already have a migration scaffold; install it as `pyfly.yaml` in the runtime working directory.
Keep `pyfly.yaml`, `alembic.ini`, `migrations/`, and `env_template` from the source archive in the working directory. The wheel does not supply the root-level `pyfly.yaml`. The release also provides a byte-identical `pyfly-26.10.3.yaml` copy for operators who already have a migration scaffold; install it as `pyfly.yaml` in the runtime working directory.

The configuration selects the PostgreSQL event adapter and separate API/worker consumer groups. Starting without it can activate framework defaults, including an attempted Kafka connection to `localhost:9092` and a separate management listener. Do not rely on those defaults for the released service.

Set the database URL, embedding model and dimensions, provider credentials, platform API keys, object-storage location, and optional Redis settings for your environment. Use [`env_template`](https://github.com/fireflyframework/flycanon/blob/v26.10.2/env_template) and the [deployment guide](https://github.com/fireflyframework/flycanon/blob/v26.10.2/docs/deployment.md) for the exact settings. Store real secrets outside published configuration and supply them through your operator's environment or secret manager.
Set the database URL, embedding model and dimensions, provider credentials, platform API keys, object-storage location, and optional Redis settings for your environment. Use [`env_template`](https://github.com/fireflyframework/flycanon/blob/v26.10.3/env_template) and the [deployment guide](https://github.com/fireflyframework/flycanon/blob/v26.10.3/docs/deployment.md) for the exact settings. Store real secrets outside published configuration and supply them through your operator's environment or secret manager.

Provision the PostgreSQL database, required extensions and roles using the deployment guide. Apply migrations as a separate operator step before serving a new database:

Expand Down
2 changes: 1 addition & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5103,7 +5103,7 @@
"url": "https://github.com/fireflyframework/flycanon/blob/main/LICENSE"
},
"title": "flycanon",
"version": "26.10.2"
"version": "26.10.3"
},
"openapi": "3.1.0",
"paths": {
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name = "flycanon"
# CalVer YY.MM.PP -- bumped per release. Note that PEP 440 normalises
# ``26.05.04`` -> ``26.5.4`` in the built wheel filename.
version = "26.10.2"
version = "26.10.3"
description = "Operational Knowledge Repository -- versioned, provenance-tracked canonical knowledge with hybrid retrieval and RAG, exposed as a standalone HTTP microservice over fireflyframework-pyfly and -agentic. Part of Firefly Framework, platform-agnostic by design."
readme = "README.md"
requires-python = ">=3.13,<3.14"
Expand Down
6 changes: 3 additions & 3 deletions sdks/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Compatible with **flycanon service version `26.10.x`**.

| SDK | Service |
|-----|---------|
| `26.10.2` | `26.10.x` (unchanged HTTP contract; Agentic dependency adoption) |
| `26.10.3` | `26.10.x` (unchanged HTTP contract; Agentic dependency adoption) |
| `26.5.7` | `26.5.x` |

## Install
Expand All @@ -50,7 +50,7 @@ Compatible with **flycanon service version `26.10.x`**.
<dependency>
<groupId>com.firefly</groupId>
<artifactId>flycanon-sdk</artifactId>
<version>26.10.2</version>
<version>26.10.3</version>
</dependency>
```

Expand Down Expand Up @@ -418,7 +418,7 @@ use).
## Versioning

The SDK pins its version to the service's CalVer (`YY.MM.PP`), so
`flycanon-sdk@26.10.2` is the matching client for service version
`flycanon-sdk@26.10.3` is the matching client for service version
`26.10.x`. Upgrade the SDK in lockstep with the service. Both
clients (`CanonClient`, `ReactiveCanonClient`) ship from the same
artifact -- no separate dependency to manage.
Expand Down
2 changes: 1 addition & 1 deletion sdks/java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<groupId>com.firefly</groupId>
<artifactId>flycanon-sdk</artifactId>
<version>26.10.2</version>
<version>26.10.3</version>
<packaging>jar</packaging>

<name>flycanon-sdk</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public final class CanonClient {
static final String HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";

/** SDK version. Mirrors {@code pom.xml}. */
public static final String SDK_VERSION = "26.10.2";
public static final String SDK_VERSION = "26.10.3";

private final RestClient restClient;
private final ObjectMapper mapper;
Expand Down
24 changes: 21 additions & 3 deletions sdks/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ Compatible with **flycanon service version `26.10.x`**.

| SDK | Service |
|-----|---------|
| `26.10.2` | `26.10.x` (unchanged HTTP contract; Agentic dependency adoption) |
| `26.10.3` | `26.10.x` (unchanged HTTP contract; Agentic dependency adoption) |
| `26.8.0` | `26.8.x` (embedding sets, `flycanon reindex`, `ChunkStats.by_embedding_model`) |
| `26.7.1` | `26.7.x` (routes realigned: `?mode=async`, `/query/stream`, `/conversations/{id}/turn`, `/query/suggest`, `?after_id=`; `X-API-Key`; `delete_source`, `purge_workspace`) |
| `26.5.7` | `26.5.x` |

## Install

```bash
uv add https://github.com/fireflyframework/flycanon/releases/download/v26.10.2/flycanon_sdk-26.10.2-py3-none-any.whl
uv add https://github.com/fireflyframework/flycanon/releases/download/v26.10.3/flycanon_sdk-26.10.3-py3-none-any.whl
# or
pip install https://github.com/fireflyframework/flycanon/releases/download/v26.10.2/flycanon_sdk-26.10.2-py3-none-any.whl
pip install https://github.com/fireflyframework/flycanon/releases/download/v26.10.3/flycanon_sdk-26.10.3-py3-none-any.whl
```

## Quick start
Expand Down Expand Up @@ -127,6 +127,24 @@ boundary.
Agent-tier callers (authenticated with `X-Agent-Token`) are refused
on the mint path with `403 agent_cannot_mint`.

Set `expires_at` to a timezone-aware datetime when a token needs a bounded lifetime. The SDK sends the expiry as an ISO 8601 JSON string and retains its timezone. For example, an authenticated platform client can mint a one-hour reader:

```python
from datetime import UTC, datetime, timedelta
from flycanon_sdk import AgentTokenMintRequest

created = await client.mint_agent_token(
AgentTokenMintRequest(
name="inventory-reader",
workspace_allowlist=["inventory"],
scopes=["agent.sources:read"],
expires_at=datetime.now(UTC) + timedelta(hours=1),
)
)
```

Store the returned token in the caller's secret store. Later token listings retain the expiry and public prefix, and omit the secret.

### Agent surface (`/api/v1/agent/*`, agent-tier, exposed via `client.agent`)

Every POST on this surface mandates a non-empty `idempotency_key`
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
[project]
name = "flycanon-sdk"
version = "26.10.2"
version = "26.10.3"
description = "Async-first Python client for the flycanon Operational Knowledge Repository service."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/src/flycanon_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
WorkspaceUpdated,
)

__version__ = "26.10.2"
__version__ = "26.10.3"

__all__ = [
"CANON_WORKSPACES_TOPIC",
Expand Down
3 changes: 2 additions & 1 deletion sdks/python/src/flycanon_sdk/_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2024-2026 Firefly Software Foundation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -964,7 +965,7 @@ async def mint_agent_token(self, request: AgentTokenMintRequest) -> AgentTokenCr
body = await self._request(
"POST",
"/api/v1/agent-tokens",
json=request.model_dump(exclude_none=True),
json=request.model_dump(mode="json", exclude_none=True),
)
return AgentTokenCreated.model_validate(body)

Expand Down
4 changes: 2 additions & 2 deletions sdks/python/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ async def test_answer_returns_typed_response() -> None:


def test_sdk_version_declares_current_release() -> None:
assert __version__ == "26.10.2"
assert __version__ == "26.10.3"


def test_canon_workspaces_topic_constant() -> None:
Expand Down Expand Up @@ -674,7 +674,7 @@ async def test_user_agent_advertises_sdk_version() -> None:
}
)
await client.version()
assert "flycanon-sdk-python/26.10.2" in route.calls.last.request.headers["User-Agent"]
assert "flycanon-sdk-python/26.10.3" in route.calls.last.request.headers["User-Agent"]


# ----------------------------------------------------------------------
Expand Down
65 changes: 65 additions & 0 deletions sdks/python/tests/test_token_expiry_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2026 Firefly Software Foundation
# SPDX-License-Identifier: Apache-2.0
"""Expiry reaches HTTP as JSON while retaining its timezone and scoped grants."""

import json
from datetime import UTC, datetime, timedelta, timezone, tzinfo

import httpx
import pytest
import respx

from flycanon_sdk import AgentTokenMintRequest, CanonClient


@pytest.mark.asyncio
@pytest.mark.parametrize("zone", [UTC, timezone(timedelta(hours=5, minutes=30))])
async def test_mint_expiring_token_serializes_datetime_at_http_boundary(zone: tzinfo) -> None:
expiry = datetime(2031, 1, 2, 3, 4, 5, tzinfo=zone)
received = []

def serve(request: httpx.Request) -> httpx.Response:
body = json.loads(request.content)
assert request.headers["X-Tenant-Id"] == "expiry-test"
assert request.headers["X-Workspace-Id"] == "workspace-test"
assert isinstance(body["expires_at"], str)
assert datetime.fromisoformat(body["expires_at"]) == expiry
assert body["scopes"] == ["agent.sources:read"]
assert body["workspace_allowlist"] == ["workspace-test"]
received.append(body)
return httpx.Response(
201,
json={
"id": "token-test",
"name": body["name"],
"prefix": "test_prefix",
"workspace_allowlist": body["workspace_allowlist"],
"scopes": body["scopes"],
"rate_limit_rpm": 20,
"expires_at": body["expires_at"],
"created_at": "2030-01-01T00:00:00Z",
"created_by": "fixture",
"token": "fictional-token",
"revoked_at": None,
"last_used_at": None,
},
)

async with (
respx.mock(base_url="http://canon") as mock,
CanonClient(
base_url="http://canon", tenant_id="expiry-test", workspace_id="workspace-test"
) as client,
):
mock.post("/api/v1/agent-tokens").mock(side_effect=serve)
result = await client.mint_agent_token(
AgentTokenMintRequest(
name="bounded-expiry",
workspace_allowlist=["workspace-test"],
scopes=["agent.sources:read"],
rate_limit_rpm=20,
expires_at=expiry,
)
)
assert len(received) == 1
assert result.expires_at == expiry
2 changes: 1 addition & 1 deletion sdks/python/uv.lock

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

2 changes: 1 addition & 1 deletion uv.lock

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

Loading