From 89dc4370489708d5bc139d4ba0f96372c5ac7195 Mon Sep 17 00:00:00 2001 From: dkijania Date: Mon, 29 Jun 2026 08:32:01 +0200 Subject: [PATCH 1/4] chore: declare 1.0.0 + add versioning & schema stability policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The package was pre-1.0 (0.0.6) with no documented stability guarantees, which public consumers need. - Bump version to 1.0.0 (package.json only — no tag; the actual release/publish remains a deliberate maintainer step via `npm version` + tag push). - Add docs/versioning.md: SemVer applied to the GraphQL schema / HTTP endpoints / config, a precise definition of breaking vs additive changes, a deprecation policy (@deprecated + one minor & 90 days before removal), and how the existing graphql-inspector "Check Schema" gate enforces it via the expected-breaking-change label. - Link the policy from the README. Closes #178. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QSuak9smCHbp4N17xjjLF6 --- README.md | 2 ++ docs/versioning.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docs/versioning.md diff --git a/README.md b/README.md index 0799213d..5780cec1 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ git push --follow-tags CI builds, publishes the npm package with provenance, 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. diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 00000000..51ded32a --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,70 @@ +# 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, or a nullable field/argument to non-null. +- 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`). + +Additive counterparts of the above (new optional field, new nullable argument, +new env var with a safe default) are **minor**, not breaking. + +## Deprecation policy + +We prefer deprecation over removal: + +1. Mark schema elements with the `@deprecated(reason: "…")` directive, pointing to + the replacement. +2. Note the deprecation in the changelog/release notes. +3. Keep the deprecated element working for **at least one minor release and 90 + days** 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 +``` + +CI then builds and publishes the npm package (with provenance) and the Docker +images. Choose the bump level according to the rules above. + +## 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.json b/package.json index f187cfa3..edc72562 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", From f59aaf0577c856d9abf424a3cd8e682243691119 Mon Sep 17 00:00:00 2001 From: dkijania Date: Fri, 17 Jul 2026 11:39:00 +0200 Subject: [PATCH 2/4] docs(versioning): add flag-gating policy; fix nullability direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codifies the practice the repo already relies on but never wrote down: changes to default response shape or exposed query surface ship default-off behind an env flag (ENABLE_BLOCK_TRANSACTION_DETAILS, ENABLED_QUERIES), flagged-off being minor and flipping the default major. This is the rule the schema checker cannot enforce — an unflagged change to a default response isn't schema-breaking, so nothing errors; the mina-explorer just blanks pages while health checks stay green. Also fixes the breaking-change direction for output fields. The doc said "a nullable field/argument to non-null" is breaking, which holds for arguments but is reversed for output fields: there the break is non-null → nullable (clients may now receive null), while nullable → non-null only strengthens the guarantee. The two are mirror images — the client supplies arguments and consumes fields — so the rules are now stated separately. Addresses review feedback on #198. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/versioning.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/versioning.md b/docs/versioning.md index 51ded32a..7fddf3af 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -19,7 +19,15 @@ Given `MAJOR.MINOR.PATCH`: GraphQL schema: - Removing or renaming a type, field, enum value, or argument. -- Changing a field's type, or a nullable field/argument to non-null. +- 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: @@ -32,6 +40,28 @@ Operational contract: 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**. + +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. + ## Deprecation policy We prefer deprecation over removal: From b6acbc50ad42aaf3e25d933e4e49281c4bbd4b69 Mon Sep 17 00:00:00 2001 From: dkijania Date: Wed, 19 Aug 2026 14:36:54 +0200 Subject: [PATCH 3/4] docs(versioning): expand stable contract policy --- README.md | 7 ++++- docs/getting-started.md | 2 +- docs/versioning.md | 65 +++++++++++++++++++++++++++++++++++++---- package-lock.json | 4 +-- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5780cec1..1b5bd027 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,12 @@ From 1.0.0 the GraphQL schema, HTTP endpoints, and configuration are a versioned ## 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 e46c25e9..f1841680 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 index 7fddf3af..df7c7a95 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -36,6 +36,11 @@ Operational contract: 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. @@ -53,6 +58,10 @@ surface). 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 @@ -62,15 +71,42 @@ 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. -2. Note the deprecation in the changelog/release notes. + 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** before removing it in a subsequent **major**. + 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. @@ -91,8 +127,27 @@ npm version # bumps package.json + creates a git tag git push --follow-tags # tag push triggers the publish pipeline ``` -CI then builds and publishes the npm package (with provenance) and the Docker -images. Choose the bump level according to the rules above. +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 diff --git a/package-lock.json b/package-lock.json index 3d576152..8a1e739d 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", From c561a2c0c546f21469ced77ba479d7452ee0c611 Mon Sep 17 00:00:00 2001 From: dkijania Date: Thu, 20 Aug 2026 23:16:26 +0200 Subject: [PATCH 4/4] docs: clarify release publishing caveat --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b5bd027..b795ccf0 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,19 @@ 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.