From bea93719f0d05892ef719961b60fd9f342d89dfa Mon Sep 17 00:00:00 2001 From: woksin Date: Fri, 25 Sep 2026 15:55:25 +0200 Subject: [PATCH 1/3] Record managed AI corpus revision fc8b4e5 cratis ai update reported no content changes beyond the recorded source revision. --- .cratis/ai.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cratis/ai.manifest.json b/.cratis/ai.manifest.json index 58b833f..0d1141c 100644 --- a/.cratis/ai.manifest.json +++ b/.cratis/ai.manifest.json @@ -1,5 +1,5 @@ { - "SourceRevision": "cc9c6312a748913c90d20c4c91b9ebd2e83edfb0", + "SourceRevision": "fc8b4e5e2953befb7ff4b80978ae5997bc77a010", "Files": [ { "Source": "agents/backend-developer.md", From 2bb3f5ab3372ed50fffab51a95288e8e495549ea Mon Sep 17 00:00:00 2001 From: woksin Date: Fri, 25 Sep 2026 15:55:26 +0200 Subject: [PATCH 2/3] Document current client status and source-checkout setup State that no client API or PyPI package exists, how the pinned contracts wheel is installed, supported Python versions, and the success signal for each required check. --- CONTRIBUTING.md | 21 +++++++++++++++++++++ Documentation/getting-started.md | 17 +++++++++++++++-- README.md | 31 +++++++++++++++++++++++++++---- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e1e9a4..085ee12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,10 @@ Thank you for helping build the Python client for Chronicle. ## Local setup +You need Python 3.10 or newer and Git. The install downloads the generated contracts wheel from a Chronicle GitHub +release, so it needs network access to GitHub release assets. Docker is needed only for work that talks to a local +Chronicle kernel; see the [client development guide](Documentation/client-development-guide.md#local-kernel). + ```shell python -m venv .venv source .venv/bin/activate @@ -34,6 +38,23 @@ python -m build python -m twine check dist/* ``` +`rm -rf dist` clears distributions from earlier builds so that Twine checks only the current build. In Windows +PowerShell, use `Remove-Item -Recurse -Force dist` instead. + +A passing run looks like this: + +| Check | Success signal | +| --- | --- | +| `ruff format --check .` | `… files already formatted` | +| `ruff check .` | `All checks passed!` | +| `mypy src` | `Success: no issues found` | +| `pytest` | All tests pass, followed by a coverage report | +| `python -m build` | `Successfully built` one `.tar.gz` and one `.whl` | +| `python -m twine check dist/*` | `PASSED` for both files | + +CI runs these checks on Python 3.10 through 3.14. Pull requests that change Markdown also run markdownlint with +the repository's `.markdownlint.json`. + All checks must pass before review. New behavior requires tests, including failure behavior where applicable. ## API principles diff --git a/Documentation/getting-started.md b/Documentation/getting-started.md index 92efa8f..36eabaf 100644 --- a/Documentation/getting-started.md +++ b/Documentation/getting-started.md @@ -1,7 +1,20 @@ # Getting started Chronicle.Python does not yet expose a usable client API or published package. This page will become the -installation and first-append guide when the initial authenticated append milestone passes its tests. +installation and first-append guide when the +[initial authenticated append milestone](https://github.com/Cratis/Chronicle.Python/issues/4) passes its tests. + +## What works today + +| You want to… | Status | +| --- | --- | +| `pip install cratis-chronicle` from PyPI | Not possible. No package is published | +| Connect to Chronicle and append events from Python | Not possible through this package yet. It exposes only `__version__` | +| Build the client from source and run its checks | Supported. See [Development setup](../README.md#development-setup) | +| Use Chronicle from another language now | Use the [.NET](https://github.com/Cratis/Chronicle), [TypeScript](https://github.com/Cratis/Chronicle.TypeScript), [Kotlin/Java](https://github.com/Cratis/Chronicle.Kotlin), or [Elixir](https://github.com/Cratis/Chronicle.Elixir) client | + +## Contribute To contribute now, follow [Building the Chronicle Python client](client-development-guide.md) and the repository -[contribution guide](../CONTRIBUTING.md). +[contribution guide](../CONTRIBUTING.md). The development guide shows how to run a local kernel and check the +token and gRPC path before you write client code. diff --git a/README.md b/README.md index b2c6021..361f3d2 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,19 @@ contracts. Python joins the existing [.NET](https://github.com/Cratis/Chronicle) and [Elixir](https://github.com/Cratis/Chronicle.Elixir) Chronicle clients. > [!IMPORTANT] -> The client is in its initial implementation stage. No package has been published, and no compatibility, -> feature-parity, or support commitment is implied. +> The client is in its initial implementation stage. It has no usable client API yet: the `cratis_chronicle` +> package exposes only `__version__`. Nothing is published to PyPI, and no compatibility, feature-parity, or +> support commitment is implied. + +## Current status + +| Area | Status | +| --- | --- | +| Client API (connect, authenticate, append) | Not implemented. Tracked by [the first authenticated append milestone](https://github.com/Cratis/Chronicle.Python/issues/4) | +| `cratis-chronicle` on PyPI | Not published. Install from a source checkout | +| Generated contracts (`cratis-chronicle-contracts`) | Not on PyPI. Installed automatically from a SHA-256-pinned wheel attached to the [Chronicle v16.38.2 release](https://github.com/Cratis/Chronicle/releases/tag/v16.38.2) | +| Python versions | 3.10 or newer; CI runs 3.10, 3.11, 3.12, 3.13, and 3.14 | +| Shared Chronicle documentation (language tabs) | Not integrated. Tracked by [Python examples in shared Chronicle documentation](https://github.com/Cratis/Chronicle.Python/issues/5) | ## Start contributing @@ -27,16 +38,28 @@ Chronicle kernel. ## Development setup -Python 3.10 or newer is required. +Python 3.10 or newer is required. The install downloads the contracts wheel from `github.com`, so it needs network +access to GitHub release assets as well as PyPI. ```shell +git clone https://github.com/Cratis/Chronicle.Python.git +cd Chronicle.Python python -m venv .venv source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 python -m pip install --upgrade pip python -m pip install -e ".[dev]" ``` -Run the same checks as CI: +Confirm that the package and its generated contracts import: + +```shell +python -c "import cratis_chronicle, cratis_chronicle_contracts; print(cratis_chronicle.__version__)" +``` + +The command prints a development version derived from Git, such as `0.0.1.dev47+g…`. It prints `0.0.0` when the +package metadata cannot be found, which usually means the editable install did not run in the active environment. + +Run the same checks as CI (the full list, with expected results, is in [CONTRIBUTING.md](CONTRIBUTING.md#required-checks)): ```shell ruff format --check . From c7fca654c36f8e6fbe006b46abd39cf3ea396982 Mon Sep 17 00:00:00 2001 From: woksin Date: Fri, 25 Sep 2026 15:55:27 +0200 Subject: [PATCH 3/3] Add verified kernel checks to the client development guide - Correct the contracts distribution status after the PyPI publishing issues closed as not planned - Pin the development kernel to 16.38.2 and bind it to loopback - Describe TLS validation boundaries and link Chronicle's client contract pages - Add a token check, a contracts-based probe, troubleshooting, and next steps --- Documentation/client-development-guide.md | 148 ++++++++++++++++++++-- 1 file changed, 135 insertions(+), 13 deletions(-) diff --git a/Documentation/client-development-guide.md b/Documentation/client-development-guide.md index 3c30ae7..dfab26a 100644 --- a/Documentation/client-development-guide.md +++ b/Documentation/client-development-guide.md @@ -21,24 +21,31 @@ Chronicle C# contracts ### Temporary contracts distribution -Until PyPI trusted publishing is configured, the project dependency resolves the verified +`cratis-chronicle-contracts` is not published to PyPI. The project dependency resolves the verified `cratis-chronicle-contracts` 16.38.2 wheel from its matching -[Chronicle GitHub release](https://github.com/Cratis/Chronicle/releases/tag/v16.38.2). A normal development install -fetches it automatically. Do not copy generated contracts into this repository. The dependency will move to the -PyPI release after [the publisher setup](https://github.com/Cratis/Chronicle.Python/issues/15) and -[first publication](https://github.com/Cratis/Chronicle.Python/issues/16) are complete. +[Chronicle GitHub release](https://github.com/Cratis/Chronicle/releases/tag/v16.38.2), pinned by SHA-256 in +`pyproject.toml`. A normal development install fetches it automatically, so the install needs network access to +GitHub release assets. Do not copy generated contracts into this repository. + +The PyPI trusted-publishing setup ([#15](https://github.com/Cratis/Chronicle.Python/issues/15)) and first +publication ([#16](https://github.com/Cratis/Chronicle.Python/issues/16)) were closed as not planned, so there is +no scheduled move to PyPI. The contracts wheel is generated from the v16.38.2 kernel; see +[Kernel version](#kernel-version) before testing against a newer kernel. ## Local kernel -The development kernel listens on `localhost:35000`. Its main listener uses TLS and serves gRPC over HTTP/2. A -development build creates a self-signed certificate and built-in client credentials. +The development kernel listens on port `35000`. Its single listener uses TLS and serves gRPC over HTTP/2, the OAuth +token endpoint, and the Workbench. The development image generates a self-signed certificate at startup and accepts +built-in development client credentials. -Start the development image: +Start the development image that matches the contracts version, bound to the loopback interface only: ```shell -docker run --rm -p 35000:35000 cratis/chronicle:latest-development +docker run --rm -p 127.0.0.1:35000:35000 cratis/chronicle:16.38.2-development ``` +The development image embeds MongoDB inside the container, so every event disappears when the container stops. + Use the explicit local-development connection string: ```text @@ -48,6 +55,13 @@ chronicle://chronicle-dev-client:chronicle-dev-secret@localhost:35000 These credentials are development defaults only. They are not production credentials or a production configuration contract. +### Kernel version + +The contracts and the probe below were exercised against `cratis/chronicle:16.38.2-development`. Newer kernels, +including `latest-development`, may add or change contracts; compatibility between the 16.38.2 contracts and a +later kernel has not been verified. Name the exact kernel image in any issue, test, or pull request that exercises +network behavior. + ## Authentication contract Request a token from: @@ -64,17 +78,106 @@ client_id= client_secret= ``` -Parse `access_token` from the JSON response. Attach the current token to each new gRPC call as metadata: +Parse `access_token` and `expires_in` (seconds) from the JSON response. Attach the current token to each new gRPC +call as metadata: ```text authorization: Bearer ``` Token acquisition, caching, expiry, refresh, and call interception should remain separate from the channel. Do -not permanently bake one expiring token into channel headers. +not permanently bake one expiring token into channel headers. Chronicle's +[authentication and bearer tokens](https://www.cratis.io/chronicle/building-a-client/authentication-and-bearer-tokens/) +page describes the behavior the other clients implement: the three authentication modes selected by the connection +string, proactive refresh before expiry, and one retry after an `UNAUTHENTICATED` response. + +### TLS and certificate validation + +The development kernel's self-signed certificate names `localhost`, `chronicle`, `127.0.0.1`, and `::1`. A Python +gRPC channel rejects it unless the certificate is supplied as a trusted root. A development option may explicitly +relax certificate verification for localhost. Production behavior must retain normal certificate and hostname +validation. + +Chronicle's .NET client differs: it accepts any server certificate unless validation is turned on; see +[TLS configuration](https://www.cratis.io/chronicle/configuration/tls/). This guide does not adopt that default. +How the Python client exposes local-development relaxation (an explicit option, a `skipTlsValidation` +connection-string parameter, or both) is a public API decision for +[connection-string parsing](https://github.com/Cratis/Chronicle.Python/issues/2). Do not decide it implicitly in +an implementation, and do not make relaxed validation the behavior for non-local connections. The connection-string grammar, including `skipTlsValidation`, `apiKey`, and `auth=none`, is in +[connection string elements](https://www.cratis.io/chronicle/building-a-client/connection-string-elements/). + +### Check the kernel before writing client code + +Confirm that the kernel issues tokens before debugging a client. With the kernel from [Local kernel](#local-kernel) +running: + +```shell +curl --insecure https://localhost:35000/connect/token \ + -d grant_type=client_credentials \ + -d client_id=chronicle-dev-client \ + -d client_secret=chronicle-dev-secret +``` + +`--insecure` skips certificate validation and is only acceptable against this local development kernel. A working +kernel returns JSON with `access_token`, `token_type` (`Bearer`), and `expires_in`. A wrong secret returns HTTP 401. + +The following probe exercises the same path through the generated contracts: it trusts the kernel's own +certificate, obtains a token, and calls `EnsureEventStore` once without and once with the bearer token. It is a +contributor diagnostic built on the internal contracts package, not the planned public client API. + +```python +import asyncio +import json +import ssl +import urllib.parse +import urllib.request + +import grpc +from cratis_chronicle_contracts.eventstores_pb2 import EnsureEventStoreRequest +from cratis_chronicle_contracts.eventstores_pb2_grpc import EventStoresStub + +HOST, PORT = "localhost", 35000 + +# Development only: trust whatever certificate the local kernel presents. +certificate = ssl.get_server_certificate((HOST, PORT)) +body = urllib.parse.urlencode( + { + "grant_type": "client_credentials", + "client_id": "chronicle-dev-client", + "client_secret": "chronicle-dev-secret", + } +).encode() +request = urllib.request.Request(f"https://{HOST}:{PORT}/connect/token", data=body) +with urllib.request.urlopen(request, context=ssl.create_default_context(cadata=certificate)) as response: + token = json.load(response)["access_token"] + + +async def main() -> None: + credentials = grpc.ssl_channel_credentials(root_certificates=certificate.encode()) + async with grpc.aio.secure_channel(f"{HOST}:{PORT}", credentials) as channel: + event_stores = EventStoresStub(channel) + try: + await event_stores.EnsureEventStore(EnsureEventStoreRequest(Name="python-probe")) + except grpc.aio.AioRpcError as error: + print("without token:", error.code().name) + result = await event_stores.EnsureEventStore( + EnsureEventStoreRequest(Name="python-probe"), + metadata=[("authorization", f"Bearer {token}")], + ) + print("with token:", type(result).__name__) + + +asyncio.run(main()) +``` -The development kernel uses a self-signed certificate. A development option may explicitly relax certificate -verification for localhost. Production behavior must retain normal certificate and hostname validation. +Run it from the activated development environment. Against `cratis/chronicle:16.38.2-development` it prints: + +```text +without token: UNAUTHENTICATED +with token: CommandResult +``` + +The probe creates an event store named `python-probe` in the development kernel. Stopping the container removes it. ## First executable milestone @@ -130,3 +233,22 @@ uncertainty against the core contracts and kernel behavior. - Local quality gates and CI pass. - Documentation names unsupported behavior and development-only exceptions. - No compatibility, parity, support, security, performance, or maturity claim was added without approval. + +## Troubleshooting + +| Symptom | Likely cause and fix | +| --- | --- | +| `pip install -e ".[dev]"` fails while downloading `cratis_chronicle_contracts-16.38.2-py3-none-any.whl` | The install cannot reach GitHub release assets. Allow `github.com` and `release-assets.githubusercontent.com`, where the download redirects, through your proxy or firewall | +| `pip` reports that hashes do not match | The downloaded wheel differs from the pinned SHA-256. Do not remove the hash; report it in an issue | +| `docker run` fails with `port is already allocated` | Another Chronicle kernel or process uses port 35000. Stop it, or publish a different host port (`-p 127.0.0.1:35100:35000`) and use that port in the connection string, the `curl` URL, and the probe's `PORT` | +| `ssl.SSLEOFError` or a refused connection right after `docker run` | The kernel is still starting. Wait until the token check succeeds, then retry | +| gRPC `UNAVAILABLE` with `CERTIFICATE_VERIFY_FAILED` in the details | The channel does not trust the kernel's self-signed certificate. Trust it explicitly for local development, as the probe does | +| gRPC `UNAUTHENTICATED` | The call carried no `authorization` metadata, or its token expired. Obtain a fresh token and attach it to every call | + +## Next steps + +- Pick up [connection-string parsing](https://github.com/Cratis/Chronicle.Python/issues/2), then + [async OAuth token handling](https://github.com/Cratis/Chronicle.Python/issues/3). +- Read Chronicle's [Building a Chronicle client](https://www.cratis.io/chronicle/building-a-client/) guide for + the cross-client contract. +- Follow [CONTRIBUTING.md](../CONTRIBUTING.md) for the required checks before opening a pull request.