diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9521451..4c2e770 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index db77dd6..e3b03d5 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ over whole documents with citations — all behind a single HTTP service.
[](https://github.com/fireflyframework/fireflyframework-agentic)
[](docs/api-reference.md)
[](docs/architecture.md#retrieval-backend-pluggable-dense-store)
-[](#)
+[](#)
[](LICENSE)
diff --git a/docs/deployment.md b/docs/deployment.md
index 67e058b..ff08191 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -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
```
@@ -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
diff --git a/docs/released-server-install.md b/docs/released-server-install.md
index d151d72..91f04db 100644
--- a/docs/released-server-install.md
+++ b/docs/released-server-install.md
@@ -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:
@@ -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'
```
@@ -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:
diff --git a/openapi.json b/openapi.json
index d11d58d..452ab1c 100644
--- a/openapi.json
+++ b/openapi.json
@@ -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": {
diff --git a/pyproject.toml b/pyproject.toml
index caef666..4fd6f68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"
diff --git a/sdks/java/README.md b/sdks/java/README.md
index 66e2fc4..aaa0a9b 100644
--- a/sdks/java/README.md
+++ b/sdks/java/README.md
@@ -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
@@ -50,7 +50,7 @@ Compatible with **flycanon service version `26.10.x`**.
com.firefly
flycanon-sdk
- 26.10.2
+ 26.10.3
```
@@ -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.
diff --git a/sdks/java/pom.xml b/sdks/java/pom.xml
index 749e7b1..96c2c13 100644
--- a/sdks/java/pom.xml
+++ b/sdks/java/pom.xml
@@ -19,7 +19,7 @@
com.firefly
flycanon-sdk
- 26.10.2
+ 26.10.3
jar
flycanon-sdk
diff --git a/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java b/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
index e77ea45..7204c9c 100644
--- a/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
+++ b/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
@@ -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;
diff --git a/sdks/python/README.md b/sdks/python/README.md
index e56d5a6..5fdf036 100644
--- a/sdks/python/README.md
+++ b/sdks/python/README.md
@@ -33,7 +33,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.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` |
@@ -41,9 +41,9 @@ Compatible with **flycanon service version `26.10.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
@@ -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`
diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml
index c0d2e5b..8dd20d6 100644
--- a/sdks/python/pyproject.toml
+++ b/sdks/python/pyproject.toml
@@ -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"
diff --git a/sdks/python/src/flycanon_sdk/__init__.py b/sdks/python/src/flycanon_sdk/__init__.py
index 7fa33df..62bdfab 100644
--- a/sdks/python/src/flycanon_sdk/__init__.py
+++ b/sdks/python/src/flycanon_sdk/__init__.py
@@ -120,7 +120,7 @@
WorkspaceUpdated,
)
-__version__ = "26.10.2"
+__version__ = "26.10.3"
__all__ = [
"CANON_WORKSPACES_TOPIC",
diff --git a/sdks/python/src/flycanon_sdk/_client.py b/sdks/python/src/flycanon_sdk/_client.py
index d3e321e..6c9731a 100644
--- a/sdks/python/src/flycanon_sdk/_client.py
+++ b/sdks/python/src/flycanon_sdk/_client.py
@@ -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.
@@ -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)
diff --git a/sdks/python/tests/test_client.py b/sdks/python/tests/test_client.py
index 46d2ea7..b4d5a55 100644
--- a/sdks/python/tests/test_client.py
+++ b/sdks/python/tests/test_client.py
@@ -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:
@@ -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"]
# ----------------------------------------------------------------------
diff --git a/sdks/python/tests/test_token_expiry_json.py b/sdks/python/tests/test_token_expiry_json.py
new file mode 100644
index 0000000..1cdac04
--- /dev/null
+++ b/sdks/python/tests/test_token_expiry_json.py
@@ -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
diff --git a/sdks/python/uv.lock b/sdks/python/uv.lock
index 1b946d9..3e5c318 100644
--- a/sdks/python/uv.lock
+++ b/sdks/python/uv.lock
@@ -44,7 +44,7 @@ wheels = [
[[package]]
name = "flycanon-sdk"
-version = "26.10.2"
+version = "26.10.3"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
diff --git a/uv.lock b/uv.lock
index 0a6c5c2..085d655 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1420,7 +1420,7 @@ wheels = [
[[package]]
name = "flycanon"
-version = "26.10.2"
+version = "26.10.3"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },