diff --git a/README.md b/README.md index 0799213..b795ccf 100644 --- a/README.md +++ b/README.md @@ -81,18 +81,30 @@ Tagged commits trigger CI to publish: - npm (public): [`@o1-labs/mina-archive-node-graphql`](https://www.npmjs.com/package/@o1-labs/mina-archive-node-graphql) - Docker (GHCR): `ghcr.io/o1-labs/archive-node-api` -To cut a release: +Normal releases after `1.0.0` are cut with: ```sh npm version git push --follow-tags ``` -CI builds, publishes the npm package with provenance, and pushes Docker tags `1.2.3`, `1.2`, `1`, `latest`. +For the initial `1.0.0` tag and current npm trusted-publishing caveat, see +the [versioning & schema stability policy](./docs/versioning.md#releasing). + +CI builds, publishes the npm package with provenance once npm trusted +publishing is configured, and pushes Docker tags `1.2.3`, `1.2`, `1`, +`latest`. + +From 1.0.0 the GraphQL schema, HTTP endpoints, and configuration are a versioned public contract — see the [versioning & schema stability policy](./docs/versioning.md) for what counts as a breaking change and how deprecations work. ## Hardware requirements -The bottleneck is the Postgres database, not this server. For production load, point `PG_CONN` at multiple read replicas — the server fans queries across them and recovers automatically as hosts come and go. A recent benchmark on a 12-core / 32 GB box (API + Postgres co-located) sustained ~800 req/s with p99 latency of 39 ms. Use `npm run benchmark` to size your own deployment. +The bottleneck is the Postgres database, not this server. Listing multiple hosts +in `PG_CONN` gives failover, not read fan-out; put a load balancer or managed +reader endpoint in front of read replicas when you need to spread query load. A +recent benchmark on a 12-core / 32 GB box (API + Postgres co-located) sustained +~800 req/s with p99 latency of 39 ms. Use `npm run benchmark` to size your own +deployment. For SLOs, capacity guidance, what to monitor, and incident response, see the [operations runbook](./docs/runbook.md). diff --git a/docs/getting-started.md b/docs/getting-started.md index e46c25e..f184168 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -76,7 +76,7 @@ Use this when you already have an archive-node Postgres reachable but don't want docker pull ghcr.io/o1-labs/archive-node-api:latest # or pin a specific version (recommended for production) -docker pull ghcr.io/o1-labs/archive-node-api:0.0.6 +docker pull ghcr.io/o1-labs/archive-node-api:1.0.0 ``` ### 2. Run diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..df7c7a9 --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,155 @@ +# Versioning & Schema Stability Policy + +From **1.0.0** onward the Archive Node API follows [Semantic Versioning](https://semver.org/) +and treats its **GraphQL schema**, **HTTP endpoints**, and **configuration** as the +public contract. + +## What the version numbers mean + +Given `MAJOR.MINOR.PATCH`: + +- **MAJOR** — a backwards-incompatible change to the public contract (see + "Breaking changes" below). Consumers may need to update queries or config. +- **MINOR** — backwards-compatible additions: new schema fields/types/arguments, + new optional config, new endpoints. Existing queries keep working. +- **PATCH** — backwards-compatible bug fixes and internal changes. + +## What counts as a breaking change + +GraphQL schema: + +- Removing or renaming a type, field, enum value, or argument. +- Changing a field's type. +- **Output fields:** making a non-null field nullable (`String!` → `String`). + Clients written against the guarantee may now receive `null` where they + cannot handle it. The reverse — `String` → `String!` — only strengthens the + guarantee and is safe. +- **Arguments and input fields:** making a nullable argument non-null + (`String` → `String!`), which rejects callers that were legitimately omitting + it. Here the reverse is the safe direction — the mirror image of output + fields, because the client is the one supplying the value. +- Adding a required (non-null, no-default) argument to an existing field. + +Operational contract: + +- Removing or renaming an environment variable, or changing its default in a way + that alters behaviour. +- Removing or renaming an HTTP endpoint (`/`, `/healthcheck`, `/readiness`, + `/metrics`). +- Raising the minimum supported Node.js runtime, whether through `engines`, the + Docker base image, or the Node version used by CI to publish the package. +- Enabling by default behaviour that can reject, throttle, or block a request + that was previously accepted, such as rate limiting, request-size caps, + query-cost limits, or a stricter CORS allowlist. + +Additive counterparts of the above (new optional field, new nullable argument, +new env var with a safe default) are **minor**, not breaking. + +## Flag-gating behaviour changes + +Changes that alter **default response shape or content**, or the **set of exposed +queries**, ship **disabled by default behind an environment flag** — the practice +this repo already follows with `ENABLE_BLOCK_TRANSACTION_DETAILS` (gates +block-detail output) and `ENABLED_QUERIES` (allowlists the exposed query +surface). + +- A flagged, default-off change is **minor**. +- Flipping such a default on — or removing the flag so the new behaviour is + unconditional — changes what existing clients receive out of the box, and is + **major**. + +Correcting a result that was demonstrably wrong is a bug fix, not a flagged +behaviour change. Call the fix out explicitly in the release notes with the +before/after shape so consumers know why content changed. + +This is what lets consumers survive upgrades. The +[mina-explorer](https://github.com/o1-labs/mina-explorer) fires fallback query +chains and degrades on the exact `"Cannot query field"` validation error, so it +tolerates a field it doesn't know about — but not a *default response* that +quietly changes shape. An unflagged change there doesn't error; it blanks +Explorer pages while every health check stays green. That failure mode is why +this is a rule rather than a convention: the schema checker cannot catch it, +because nothing about the schema is technically breaking. + +## Error messages and validation behaviour + +GraphQL validation and parse errors are part of the public contract. Clients use +them for capability detection: they probe for a field or filter and fall back +based on the error text. + +Covered by this policy: + +- Validation and parse errors must be returned in `errors[]` with their verbatim + `graphql-js` wording, including `Cannot query field "X" on type "Y".`, + `Unknown argument "X" on field "Y".`, `Unknown type "X".`, and unknown + input-field errors that name the field, such as `inBestChain`. +- `errors[]` must still be present in the response body when the HTTP status is + non-2xx; clients parse the body regardless of status code. +- Error masking applies to unexpected thrown runtime errors only. Widening it to + cover validation or parse errors, or replacing their text with a generic + string, error code, or redacted message, is **major**. + +Known consumers match this text today: +[mina-explorer](https://github.com/o1-labs/mina-explorer) checks for +`inBestChain`, while mina-explorer-api checks for `Cannot query field`, +`Unknown argument`, `Unknown type`, and `inBestChain`. As with flag-gating, +breaking this does not fail loudly: the schema checker stays green, health +checks stay green, and consumers may serve empty views. + +## Deprecation policy + +We prefer deprecation over removal: + +1. Mark schema elements with the `@deprecated(reason: "…")` directive, pointing to + the replacement and planned removal target, for example + `"Use X. Removed in 2.0.0, no earlier than 2026-11-15."`. +2. Announce the deprecation in the GitHub release notes for the minor that + introduces it. The 90-day clock starts when that release is published. +3. Keep the deprecated element working for **at least one minor release and 90 + days**, whichever is later, before removing it in a subsequent **major**. + +Environment variables follow the same path: continue honouring the old name +(with a startup warning) for one minor + 90 days before removal. + +## Enforcement + +Schema changes are checked in CI by **graphql-inspector** (the "Check Schema" +job). A change it flags as breaking fails the build unless the PR carries the +`expected-breaking-change` label — so every breaking change is a deliberate, +reviewed decision that must be paired with a major version bump. + +## Releasing + +Releases are cut from `main` by a maintainer: + +```sh +npm version # bumps package.json + creates a git tag +git push --follow-tags # tag push triggers the publish pipeline +``` + +For the initial `1.0.0` release only, `package.json` on `main` already carries +the version to release. Tag it directly (`git tag v1.0.0 && git push +--follow-tags`) rather than running `npm version`, which would bump past it. + +CI then builds and publishes the npm package (with provenance, once npm trusted +publishing is configured for this repository) and the Docker images. Choose the +bump level according to the rules above. + +## Migrating from npm `0.0.6` + +Tags `0.0.7` through `0.0.9` existed in git but were not published to npm, so +npm consumers should treat `1.0.0` as an upgrade from `0.0.6`. Review these +operator-visible changes before rolling out: + +- Browser deployments must set `CORS_ORIGIN` deliberately. +- Rate limiting is enabled and depends on the correct `TRUST_PROXY` hop count. +- The supported Node.js runtime moves to Node 22. +- Boolean environment variables reject junk values instead of relying on + JavaScript truthiness. +- `actions` result semantics include correctness fixes called out in the release + notes. + +## Supported versions + +The latest released **MAJOR.MINOR** receives bug and security fixes. Older lines +are supported on a best-effort basis. diff --git a/package-lock.json b/package-lock.json index 3d57615..8a1e739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@o1-labs/mina-archive-node-graphql", - "version": "0.0.9", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@o1-labs/mina-archive-node-graphql", - "version": "0.0.9", + "version": "1.0.0", "license": "ISC", "dependencies": { "@envelop/core": "^4.0.0", diff --git a/package.json b/package.json index f187cfa..edc7256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@o1-labs/mina-archive-node-graphql", - "version": "0.0.9", + "version": "1.0.0", "description": "A NodeJS GraphQL server for exposing Mina Protocol archive node data for o1js/zkApps", "repository": { "type": "git",