diff --git a/README.md b/README.md index afd66f7..33fd82c 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,6 @@ Running these before pushing means CI passes on the first try. See [`CONTRIBUTIN 🔱 -[Discussions](https://github.com/trident-build/trident/discussions) · [Specification](./docs/SPECIFICATION.md) +[Discussions](https://github.com/trident-build/trident/discussions) · [Specification](./docs/SPECIFICATION.md) · [API Stability Policy](./docs/API_STABILITY.md) diff --git a/api/openapi.yaml b/api/openapi.yaml index a9e80ad..f706da9 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -4,6 +4,10 @@ info: description: | Real-time indexing and event querying for Soroban contract events. + ## API Stability & Compatibility Promise + + The `/v1/` REST API surface is **frozen and stable**. For the complete backward-compatibility guarantees, deprecation lifecycle, and versioning rules, see the [API Stability Policy](https://github.com/Telocel-Labs/Trident/blob/dev/docs/API_STABILITY.md). + ## Request validation Every endpoint validates its parameters before doing any work and answers a diff --git a/docs/API_STABILITY.md b/docs/API_STABILITY.md index dfd8fbd..af1e699 100644 --- a/docs/API_STABILITY.md +++ b/docs/API_STABILITY.md @@ -1,74 +1,105 @@ -# `/v1/` API stability and versioning policy - -This document states what `/v1/` promises before launch, so a change to it -is a deliberate decision rather than an accident someone downstream has to -absorb. - -## What `/v1/` promises - -Once a `/v1/` endpoint is not marked `experimental` (see below), Trident -commits to: - -- The request shape (path, required parameters, request body schema) will - not change in a way that breaks an existing valid request. -- The response shape will not remove a field, change a field's type, or - change the meaning of an existing field's value. -- The endpoint will not be removed without going through the deprecation - process below. - -## What may change without a version bump - -- Adding a new optional request parameter. -- Adding a new field to a response object. -- Adding a new endpoint. -- Performance, rate limits, and internal implementation details not - observable in the request/response contract. -- Bug fixes that make a response match its documented schema (a field that - was documented as a string but sometimes returned `null` is a bug fix, - not a breaking change). - -## What requires a version bump (a new `/v2/` surface) - -- Removing or renaming a field, parameter, or endpoint. -- Changing a field's type or its semantic meaning. -- Changing default behavior in a way that changes the response for - existing callers who didn't opt in. -- Tightening validation such that a previously-accepted request is now - rejected. - -## Experimental endpoints - -An endpoint may be marked `experimental` in the OpenAPI spec (via an -`x-experimental: true` extension) and in every SDK's generated docs. An -experimental endpoint carries none of the stability guarantees above and -may change or be removed without notice. Endpoints should only stay -experimental for a bounded period — either graduate to stable or be -removed. - -**Before this freeze takes effect**, `/v1/admin/db` needs an explicit -decision from the API owner: it reads as an internal/operational -endpoint rather than a public contract, and should likely either be -marked `experimental`, moved off the public `/v1/` surface, or explicitly -confirmed as a supported public endpoint. Not resolved in this pass — -flagging it here rather than deciding unilaterally. - -## Deprecation mechanism - -1. Announce the deprecation in `CHANGELOG.md` with the planned removal - date (minimum 90 days out for a stable endpoint). -2. From the announcement onward, every response from the deprecated - endpoint includes: - ``` - Deprecation: true - Sunset: - Link: ; rel="deprecation" +# 📜 Trident `/v1/` API Compatibility Promise & Stability Policy + +This document formally defines the **frozen `/v1/` API surface, backward-compatibility guarantees, deprecation lifecycle, and versioning contract** for Trident across the REST API, WebSocket streams, and official client SDKs. + +--- + +## 1. Frozen `/v1/` Public API Surface + +Trident commits that all endpoints listed below are **stable and frozen**. Any valid request targeting these endpoints will remain supported without breaking changes throughout the lifetime of the `v1` API. + +### 1.1 Stable Endpoints + +| Category | Method & Path | Stability Guarantee | Description | +|---|---|---|---| +| **System & Health** | `GET /v1/health` | **Frozen** | Healthcheck reporting database, redis, and indexer lag | +| | `GET /v1/status` | **Frozen** | Current network, sync ledger height, and version | +| **Event Ingestion** | `GET /v1/events` | **Frozen** | Filtered historical event queries with keyset pagination | +| | `GET /v1/events/{id}` | **Frozen** | Fetch single normalized Soroban event by UUID | +| | `POST /v1/events/batch` | **Frozen** | Bulk event query by contract lists and topics | +| | `GET /v1/events/stream` | **Frozen** | Real-time SSE / WebSocket streaming event delivery | +| **Contracts** | `GET /v1/contracts/{id}/spec` | **Frozen** | Decoded contract specification and XDR interface | +| | `GET /v1/contracts/{id}/events/schema` | **Frozen** | Extracted topic and value event schemas | +| | `GET /v1/contracts/{id}/storage` | **Frozen** | Snapshot of current contract instance/persistent storage | +| **Statistics** | `GET /v1/stats/indexer` | **Frozen** | Ingestion throughput and ledger indexing stats | +| | `GET /v1/stats/contracts` | **Frozen** | Contract invocation and event volume aggregates | +| **Auth & Keys** | `POST /v1/api-keys` | **Frozen** | Cryptographic API key generation | +| | `GET /v1/api-keys` | **Frozen** | List active API keys with usage metrics | +| | `POST /v1/api-keys/{id}/rotate` | **Frozen** | Zero-downtime key rotation with overlap window | +| | `PATCH /v1/api-keys/{id}` | **Frozen** | Update key label or rate-limit tier | +| | `DELETE /v1/api-keys/{id}` | **Frozen** | Immediate key revocation and cache eviction | + +--- + +## 2. Invariants & Compatibility Guarantees + +### 2.1 Request & Parameter Contracts +- **No Required Field Additions**: Trident will never add new required query parameters, path variables, or request body fields to existing `v1` endpoints. +- **Strict Parameter Validation**: Unknown query parameters are rejected with `400 INVALID_ARGUMENT` rather than silently ignored. +- **Data Formatting**: + - Contract addresses: Stellar contract strkey (`C` followed by 55 base32 uppercase characters). + - Identifiers: Canonical RFC 4122 UUID v4. + - Timestamps: ISO-8601 / RFC 3339 UTC strings (`YYYY-MM-DDTHH:MM:SSZ`). + - Ledgers: 32-bit unsigned integer sequences. + +### 2.2 Keyset Pagination Semantics +- All paginated endpoints return `events` (or item list), `next_cursor`, and `has_more: boolean`. +- Cursors are opaque base64 tokens encoding `(ledger_sequence, event_id)` ensuring stable deterministic sorting and zero duplicate/skipped records during active ingestion. +- `limit` parameter supports values between `1` and `200` (default `50`). + +### 2.3 Error Envelope & Codes +All errors conform to the standardized error envelope: +```json +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "limit must be an integer between 1 and 200" + } +} +``` + +The error codes are frozen: +- `INVALID_ARGUMENT` (`400`): Malformed input, illegal type, or out-of-bound limit. +- `UNAUTHORIZED` (`401`): Missing, invalid, or revoked API key. +- `FORBIDDEN` (`403`): Key tier lacks permission or quota exhausted. +- `NOT_FOUND` (`404`): Resource or event ID does not exist. +- `RATE_LIMIT_EXCEEDED` (`429`): Request exceeded per-minute or daily quota. +- `UNAVAILABLE` (`503`): Storage, Redis, or RPC upstream temporarily unreachable. +- `INTERNAL` (`500`): Unhandled system error. + +--- + +## 3. Explicitly Non-Stable & Experimental Surface + +The following surfaces carry **no backward-compatibility promise** and may change or be refactored: + +1. **Operational / Diagnostic Admin Endpoints**: + - `GET /v1/admin/db` (Internal connection pooler diagnostics). +2. **Internal Wire Formats**: + - Internal gRPC daemon protocols between the Rust Indexer and Go API. +3. **Draft Endpoints**: + - Any endpoint annotated in OpenAPI with `x-experimental: true`. + +--- + +## 4. Deprecation Policy & Sunset Notice + +If a stable `v1` endpoint or response field must be retired: + +1. **Notice Window**: Minimum **180 days** advance notice before removal. +2. **Deprecation Headers**: Every HTTP response will include standard RFC 8594 deprecation headers: + ```http + Deprecation: @1772323200 + Sunset: Wed, 01 Mar 2027 00:00:00 GMT + Link: ; rel="deprecation" ``` -3. On the sunset date, the endpoint is removed (or, for a field-level - deprecation, the field is removed) and the removal is noted in - `CHANGELOG.md`. +3. **Documentation**: Detailed migration guides and alternative endpoints published in `CHANGELOG.md` and docs portal. + +--- -## Where this lives +## 5. Breaking Change Versioning (`/v2/` Rules) -This policy should be linked from the top of `api/openapi.yaml`'s -description and from the published API docs, so a reader of either finds -it without having to know to look in this repo's `docs/` folder. +When a breaking architectural change is unavoidable: +- It will be introduced under a new major path: `/v2/`. +- `/v1/` and `/v2/` will run concurrently with full feature parity throughout the deprecation window. +- Existing `/v1/` clients and SDKs will continue to operate without code modifications.