Skip to content

Add OAuth token provider and first authenticated append - #39

Draft
einari wants to merge 7 commits into
feature/connection-string-parserfrom
feature/authenticated-append
Draft

einari wants to merge 7 commits into
feature/connection-string-parserfrom
feature/authenticated-append

Conversation

@einari

@einari einari commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #33 (connection strings): this branch is based on feature/connection-string-parser. Retarget to main after #33 merges.

Added

  • Async OAuth client-credentials token provider: form POST to /connect/token, typed errors (TokenAuthorizationError, TokenRequestError, TokenResponseError), conservative 30 second caching when expires_in is absent, proactive refresh, one shared refresh for concurrent callers, and cancellation that does not break other callers. (Implement async OAuth token handling #3)
  • Bearer token on every new gRPC call; the token is looked up per call, never fixed into the channel. (Implement async OAuth token handling #3)
  • ChronicleClient: ensures an event store and the Default namespace, registers an event type with a non-empty JSON schema and appends to event-log, returning the sequence number. Channel and token resources close deterministically. (Complete the first authenticated append milestone #4)
  • Wire conversion for UUIDs, dates, times, durations and concept wrappers.
  • Runnable sample (Samples/append_event), a real getting-started page, an authentication and TLS page, and opt-in integration tests against a development kernel.

Changed

  • skipTlsValidation (default true) now has behavior: it trusts the local kernel's self-signed certificate only for localhost and loopback addresses. Every other host is verified against trusted roots, and ca_certificates supplies a private CA. Host names are always checked.
  • grpcio is a direct dependency.

Known limits

  • Uses the cratis-chronicle-contracts 19.31.3 wheel (GitHub release asset, SHA-256 pinned; PyPI publication is not planned). The client calls the 19.x contract names, so 16.x kernels are not supported: event type registration fails with UNIMPLEMENTED there. Verified against cratis/chronicle:19.31.3-development.
  • Not included: reactors, projections, reconnect, discovery, retry on UNAUTHENTICATED.

Fixed

  • Invalidating the token provider while a refresh was in flight no longer lets that refresh put its stale token back in the cache, and callers after the invalidation no longer join it.
  • A failed shared token refresh whose callers were all cancelled no longer leaves a never-retrieved exception.
  • A failed event type registration is reported as an error instead of being ignored.

(#3, #4)

einari added 4 commits October 3, 2026 13:03
Request a token from /connect/token with a form body, cache it until shortly
before expiry, share one in-flight refresh between callers and keep secrets out
of repr and errors. Certificates and host names are always verified; a
self-signed development certificate is trusted only for loopback hosts.
Add the bearer-token channel interceptor, wire-value conversion, and a client
that ensures an event store and namespace, registers a schema and appends to
the event log. Declare grpcio, which the client now imports directly, and
register the integration marker.
@einari einari added the minor Backward-compatible capability addition label Oct 3, 2026
einari added 3 commits October 3, 2026 13:12
…ote hosts

The transport did a single read, so a response split across TCP segments
parsed as incomplete or truncated; read until close, within the size
bound, and reject a body shorter than its Content-Length. Refuse to send
the client secret or a token without TLS to any host but localhost.
…lures

Invalidating while a refresh was in flight let that refresh repopulate the
cache with a token issued before the invalidation. Each refresh now carries
the generation it started in, and later callers no longer join a refresh
that was detached by invalidation.

When every caller waiting on a shared refresh was cancelled, a failing
refresh surfaced as a never-retrieved exception. Callers now wait without
a shield wrapper and every refresh outcome is retrieved when it settles.
Pin the published 19.31.3 contracts wheel and call the renamed services:
EventTypes.RegisterEventTypes (now checked for a failed command result) and
Sequences.EventSequences.Append, whose response is wrapped in a command
result. Event type contracts stay dynamically imported because their stub
still declares an enum member named None. Document the 19.31.3 kernel and
that 16.x kernels are no longer supported.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Backward-compatible capability addition

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant