> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phosra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Versioning & Upgrades

> The semver commitment for the @phosra/* and @openchildsafety/* packages, how an SDK version maps to the API version, the deprecation / EOL windows, and real worked "upgrade from vX to vY" migrations — with versions pulled live from npm.

This is the lifecycle contract for the **published SDK packages** — how they are
versioned, how their version relates to the wire API, when they can break, and how to
move from one version to the next. For the *wire* API's four version layers (the
`/api/v1` path, the OCSS spec version, Annex B editions), start with
[Versioning](/versioning); this page is the package-level companion.

<Info>
  **Pulled live.** Every version, publish date, and status on this page was read from
  the public **npm registry** — the [version history](https://www.npmjs.com/package/@phosra/gatekeeper?activeTab=versions)
  and per-package **README** — and cross-checked against the docs
  [Changelog](/changelog) on **2026-07-06**. Nothing here is hand-maintained — if a
  number looks stale, `npm view` it and open an issue.
</Info>

## Published packages, today

These are the packages actually on the public npm registry, newest release date first.
Pin the ones you use.

| Package                                                                        | Latest  | First published | Version history                         | What it is                                                                           |
| ------------------------------------------------------------------------------ | ------- | --------------- | --------------------------------------- | ------------------------------------------------------------------------------------ |
| [`@openchildsafety/ocss`](https://www.npmjs.com/package/@openchildsafety/ocss) | `0.1.4` | 2026-06-28      | `0.1.0 → 0.1.1 → 0.1.2 → 0.1.3 → 0.1.4` | OCSS protocol + RFC 9421 signing / verification, sealed envelopes, trust-list verify |
| [`@phosra/gatekeeper`](https://www.npmjs.com/package/@phosra/gatekeeper)       | `0.3.0` | 2026-06-28      | `0.1.0 → 0.2.0 → 0.2.1 → 0.2.2 → 0.3.0` | Platform-side decision engine — verify profiles, enforce, send §8.3.8 receipts       |
| [`@phosra/link`](https://www.npmjs.com/package/@phosra/link)                   | `0.1.2` | 2026-06-28      | `0.1.0 → 0.1.1 → 0.1.2`                 | Writer-plane SDK — the consent ceremony + signed rule-write directives               |
| [`@phosra/mcp`](https://www.npmjs.com/package/@phosra/mcp)                     | `0.4.0` | 2026-05-15      | `0.2.0 → 0.4.0`                         | MCP server exposing Phosra tools to AI agents (`npx @phosra/mcp`)                    |
| [`@phosra/cli`](https://www.npmjs.com/package/@phosra/cli)                     | `0.2.0` | 2026-06-28      | `0.1.0 → 0.1.1 → 0.2.0`                 | Partner CLI — scaffold, verify OCSS setup, sandbox round-trips                       |
| [`@phosra/sdk`](https://www.npmjs.com/package/@phosra/sdk)                     | `0.1.0` | 2026-06-28      | `0.1.0`                                 | Typed control-plane client (`PhosraClient`)                                          |
| [`@phosra/connect`](https://www.npmjs.com/package/@phosra/connect)             | `0.1.1` | 2026-07-01      | `0.1.0 → 0.1.1`                         | Embeddable Connect component (web + React Native)                                    |
| [`@phosra/classify`](https://www.npmjs.com/package/@phosra/classify)           | `0.1.1` | 2026-06-29      | `0.1.0 → 0.1.1`                         | Rule-category classification helpers                                                 |
| [`@phosra/provider`](https://www.npmjs.com/package/@phosra/provider)           | `0.1.0` | 2026-06-29      | `0.1.0`                                 | Provider-side helpers over `@openchildsafety/ocss`                                   |

<Note>
  There is **no `@ocss/*` npm scope**. The open standard's reference library ships as
  **`@openchildsafety/ocss`**; Phosra's own SDKs ship under **`@phosra/*`**. If a guide
  anywhere tells you to `npm install @ocss/…`, it is wrong — that install 404s.
  The native **iOS / Android** enforcement SDKs are distributed privately (not on
  npm/Maven Central) — see [iOS SDK](/sdks/ios) and [Android SDK](/sdks/android).
</Note>

Verify any row yourself — this prints the live registry state, no auth:

```bash npm theme={null}
npm view @openchildsafety/ocss version   # → 0.1.4
npm view @phosra/gatekeeper version      # → 0.3.0
npm view @phosra/mcp version             # → 0.4.0
npm view @phosra/sdk versions            # → [ '0.1.0' ]
```

## The semver commitment

The packages follow [Semantic Versioning 2.0.0](https://semver.org/). Every package
above is currently **pre-1.0 (`0.x`)**, and that changes what a version bump promises.

| Bump                                       | `1.x` and later (future)                 | `0.x` (today)                                                                                                    |
| ------------------------------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Patch** (`0.1.1 → 0.1.2`)                | Bug fixes only, no API change            | Bug fixes only, no API change                                                                                    |
| **Minor** (`0.1.x → 0.2.0`)                | New features, backward-compatible        | **May include breaking changes** — this is what `0.x` *means* under [SemVer §4](https://semver.org/#spec-item-4) |
| **Major** (`0.x → 1.0`, later `1.x → 2.0`) | Breaking changes, with a migration guide | The `1.0.0` line stabilizes the public API and starts the standard guarantees                                    |

<Warning>
  **Pin exact while we are `0.x`.** Because a `0.x` **minor** bump can break, do not
  float on a caret range (`^0.1.0`) in production — an `npm install` could silently
  pull a breaking `0.2.0`. Pin the exact version you tested and bump on purpose after
  reading the changelog.
</Warning>

```bash npm theme={null}
# Reproducible — pin exact, no surprise breaks
npm install @phosra/sdk@0.1.0 @openchildsafety/ocss@0.1.4 @phosra/gatekeeper@0.3.0
```

What every version *does* promise, even at `0.x`:

<CardGroup cols={2}>
  <Card title="Patches never break" icon="check">
    A patch bump (`0.2.1 → 0.2.2`) is bug-fix-only. Safe to take without a code change.
  </Card>

  <Card title="Breaks are documented" icon="book">
    A breaking minor lands with a changelog entry and a before/after migration — like
    the `@phosra/gatekeeper` `0.1 → 0.2` example [worked below](#worked-example-phosra-gatekeeper-0-1-x-0-2-x).
  </Card>

  <Card title="Published bytes are immutable" icon="lock">
    A published version is never re-published with different bytes. `0.1.3` is `0.1.3`
    forever — new code gets a new number.
  </Card>

  <Card title="Deprecated exports are flagged" icon="triangle-exclamation">
    Anything on the way out carries a `@deprecated` JSDoc tag your editor and `tsc`
    surface before it is removed.
  </Card>
</CardGroup>

## How an SDK version maps to the API version

There are **three independent version axes**, and the SDK's semver is decoupled from both
wire axes. The SDK talks to the stable `/api/v1` path and stamps the current OCSS
**spec version** into the signed envelopes it builds; bumping the SDK never changes which
API version or spec version you hit, and either wire axis evolving additively never forces
an SDK bump.

|                   | **SDK version** (`@phosra/sdk@0.1.0`) | **API version** (`/api/v1`)                           | **OCSS spec version** (`OCSS-Spec-Version`)                                                          |
| ----------------- | ------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Where**         | your `package.json`                   | the request URL **path**                              | a **request header** on signed census calls, and the `ocss_version` field inside every OCSS envelope |
| **Today's value** | per-package semver (e.g. `0.1.0`)     | `v1`                                                  | `OCSS-v1.0-pre`                                                                                      |
| **Cadence**       | normal library releases               | only a genuinely incompatible contract cuts `/api/v2` | the protocol itself revs; the census negotiates the highest common version                           |
| **Who moves it**  | you, by upgrading the package         | Phosra, and `v1` keeps serving in parallel            | the OCSS steward; the SDK sets it for you                                                            |
| **Breaking?**     | a `0.x` minor may break *your build*  | a new **path**, never a mutation of `v1`              | negotiated fail-closed — an unsupported version is rejected, never silently downgraded               |

Both the pinned-`0.1.0` SDK and a hand-rolled `curl` client hit the exact same `v1`
surface. Proof — the SDK's default `baseUrl` is `/api/v1`, and there is no `v2` yet:

```bash curl theme={null}
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s -o /dev/null -w "v1 → %{http_code}\n" "$BASE/api/v1/platforms"   # → v1 → 200
curl -s -o /dev/null -w "v2 → %{http_code}\n" "$BASE/api/v2/platforms"   # → v2 → 404 (no v2 yet)
```

<Note>
  **The spec-version header is not the SDK version.** The `OCSS-Spec-Version` request
  header (and the matching `ocss_version` field the writer-plane SDKs stamp into each
  signed envelope) identifies the **protocol** edition — `OCSS-v1.0-pre` today — and is
  covered by your RFC 9421 signature. It moves on the OCSS steward's cadence, not on any
  `@phosra/*` release. So upgrading `@phosra/gatekeeper` from `0.1` to `0.2` does **not**
  change the spec version you speak, and a future `OCSS-v1.0` does not force an SDK bump —
  the census [negotiates](/versioning#ocss-spec-version) the highest version you both
  support and fails closed if there is no overlap. The three axes rev on three clocks. See
  [Versioning → the four layers](/versioning) for the full picture.
</Note>

<Note>
  **The forward-compatibility rule the SDKs are built for.** Inside `v1`, Phosra adds
  new endpoints, new **optional** request fields, and new **response** fields / enum
  values without a major bump. The typed clients deserialize leniently and ignore
  unknown fields, so an additive API change reaches you as data — **no reinstall
  required**. See [Forward compatibility](/integration/forward-compatibility).
</Note>

## Deprecation & end-of-life

SDK deprecations ride the same [Deprecation & Sunset policy](/deprecation) as the wire
API, expressed in package terms:

* **A deprecated export keeps working.** It gains a `@deprecated` JSDoc tag naming the
  replacement; `tsc` and your editor flag it, but the runtime behavior is unchanged.
* **Removal follows semver.** At `0.x`, a deprecated export can be removed in the next
  **minor** (`0.2 → 0.3`). Once a package reaches `1.0`, a deprecated export survives
  until the next **major** and its removal is called out in the changelog.
* **The wire windows still protect you.** The `≥ 90-day` deprecation window and
  `≥ 12-month` major-version overlap in the [Deprecation policy](/deprecation#advance-notice-windows)
  govern the **API surface the SDK calls** — so even if you pin an old SDK, the
  endpoints it depends on do not vanish under it without notice.

<Warning>
  A **floating SDK caret range does not inherit the wire API's notice windows.** The
  `≥ 90-day` guarantee is about the *endpoint*; a `0.x` **minor** SDK bump is the one
  lane that can break with no separate window. That is the trade you make by not pinning
  exact. See [Deprecation → pre-1.0 SDKs](/deprecation).
</Warning>

## Native iOS & Android SDK lifecycle

The npm packages above are only the control- and writer-plane. The **on-device
enforcement** SDKs — `PhosraSDK` (iOS, Swift) and `com.phosra:sdk` (Android, Kotlin) —
are distributed **privately** (not npm, not Maven Central, not Swift Package Index), so
their lifecycle is worth stating explicitly. Full API guides: [iOS SDK](/sdks/ios) and
[Android SDK](/sdks/android).

<Warning>
  **The private SDKs are in preview.** During private preview a new tag can carry a
  breaking change with no separate notice window — exactly like the `0.x` npm minors.
  **Pin the exact version Phosra gives you** and read the release note emailed to the
  preview list before bumping. Email
  [developers@phosra.com](mailto:developers@phosra.com) for repo/AAR access.
</Warning>

### Version scheme & how you pin

|                                         | **iOS — `PhosraSDK`**                                                    | **Android — `com.phosra:sdk`**                                                                                  |
| --------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| **Distribution**                        | private Git repo, consumed via **Swift Package Manager**                 | private **Maven** repo (URL + credentials from Phosra)                                                          |
| **Version identifier**                  | a **semver Git tag** on the private repo                                 | a **semver Maven coordinate** — today `com.phosra:sdk:1.0.0`                                                    |
| **How to pin exact**                    | SPM `.exact("x.y.z")` in `Package.swift` (or the pinned tag in Xcode)    | the fully-qualified coordinate, e.g. `implementation("com.phosra:sdk:1.0.0")` — **never** a `+` dynamic version |
| **Platform floor (the real EOL lever)** | iOS **16.0+**, Swift **5.9+**, Xcode **15+**, FamilyControls entitlement | Android **8.0+ (API 26)**, Kotlin **1.9+**, Gradle **8+**, compileSdk/targetSdk **35**                          |

Pin the exact tag/coordinate — the same discipline as pinning an npm `0.x`:

<CodeGroup>
  ```swift Package.swift (iOS — pin exact) theme={null}
  // URL is the private repo Phosra provides; the version is a semver Git tag.
  .package(url: "<private PhosraSDK repo URL>", exact: "1.0.0"),
  // …then add "PhosraSDK" to your target's dependencies.
  ```

  ```kotlin build.gradle.kts (Android — pin exact) theme={null}
  dependencies {
      implementation("com.phosra:sdk:1.0.0") // exact coordinate, never "1.0.+" or "latest.release"
  }
  ```
</CodeGroup>

### Upgrade delivery — no `npm outdated`

Because these SDKs are not on a public registry, you cannot `npm outdated` them. Upgrade
delivery works differently:

* **You are notified by email** (the private-preview list) when a new tag/coordinate is
  cut, with its release note. There is no public dist-tag to poll.
* **iOS:** bump the `exact:` version in `Package.swift` (or the pinned tag in Xcode) and
  re-resolve the package graph.
* **Android:** bump the `com.phosra:sdk:x.y.z` coordinate and re-sync Gradle.
* **Re-test on-device**, not just build — an enforcement behavior change (like the
  gatekeeper's staleness floor) compiles clean but changes runtime verdicts.

<Note>
  **The on-device *policy* version is a different axis from the *SDK* version.** The
  `policy.version` integer you `ackPolicyVersion(…)` on-device increments **every time a
  parent changes a rule** and is delivered over the wire at runtime (the device sends
  `sinceVersion` and gets a `304` when nothing changed) — see
  [iOS SDK → policy sync](/sdks/ios) and [Android SDK → policy sync](/sdks/android). That
  is **not** a library upgrade: it moves on the parent's cadence, needs no rebuild, and is
  unrelated to the SPM tag / Maven coordinate. Two separate clocks — don't conflate them.
</Note>

### Deprecation, EOL & support matrix

| Lifecycle question              | Private iOS / Android SDKs (today)                                                                                                            | At public GA (future)                                                                                       |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Can a version break me?**     | Yes — a preview tag may break, like a `0.x` npm minor. Pin exact; read the emailed release note.                                              | No: semver applies — breaking changes only on a **major**, with a migration guide.                          |
| **What sets end-of-life?**      | The **OS floor** above. When Apple/Google drop an OS release, or the FamilyControls entitlement policy changes, support follows the platform. | OS floor **plus** the standard [`≥ 12-month` major-version overlap](/deprecation#advance-notice-windows).   |
| **How is a removal announced?** | By email to the preview list, ahead of the tag that removes it.                                                                               | Changelog entry + a `@deprecated`-equivalent symbol annotation before removal.                              |
| **Support channel**             | [developers@phosra.com](mailto:developers@phosra.com) (private preview — no public LTS yet).                                                  | Public support + the wire-API [deprecation windows](/deprecation) that protect the endpoints the SDK calls. |

<Note>
  The `≥ 90-day` / `≥ 12-month` wire windows in the
  [Deprecation policy](/deprecation#advance-notice-windows) protect the **census
  endpoints** these native SDKs call (policy fetch, receipt POST) regardless of SDK
  distribution — so even a pinned old on-device build keeps talking to a stable `/api/v1`.
</Note>

## Per-package changelogs

The canonical, human record of every release is the [Changelog](/changelog). Package
detail lives with the package:

<CardGroup cols={2}>
  <Card title="Changelog (all packages)" icon="clock-rotate-left" href="/changelog">
    Dated release notes for the API, platform conventions, and every SDK line — newest
    first.
  </Card>

  <Card title="@phosra/gatekeeper README" icon="shield-check" href="https://www.npmjs.com/package/@phosra/gatekeeper?activeTab=readme">
    The gatekeeper README documents the behavior contract — the §8.3.6 revoked-tombstone
    / `endpointState()` handling and the `0.x` stability policy. The dated
    version-by-version record lives in the [Changelog](/changelog).
  </Card>

  <Card title="npm version history" icon="box" href="https://www.npmjs.com/package/@phosra/sdk?activeTab=versions">
    Every published version and its release date, per package, straight from npm.
  </Card>

  <Card title="SDK overview" icon="cubes" href="/sdks/overview">
    What each package is, which are published vs. private, and the language coverage
    matrix.
  </Card>
</CardGroup>

## Worked example — `@phosra/gatekeeper` `0.1.x → 0.2.x`

This is a **real** breaking upgrade (recorded in the [Changelog](/changelog); the
behavior contract it introduced is documented in the
[`@phosra/gatekeeper` README](https://www.npmjs.com/package/@phosra/gatekeeper?activeTab=readme)),
and the best illustration of why `0.x` minors demand attention. The gatekeeper is the
platform-side enforcement engine, so its `0.2.0` "safety hardening" release deliberately
changed a runtime behavior.

**What changed (`0.1.0 → 0.2.0`, published 2026-06-28):**

<CardGroup cols={2}>
  <Card title="Bounded-staleness floor (behavior change)" icon="clock">
    A new `profileMaxAgeMs` config field (default **60 000 ms**) makes `isAllowed`
    return a **`block`** verdict for any cached enforcement profile older than the
    floor, *independent of* the census-signed window. Partners relying on long census
    windows see **more `block` verdicts** unless they `refreshProfile()` within it.
  </Card>

  <Card title="Two new lifecycle surfaces" icon="shield">
    `endpointState()` and the `onRevoked` config callback (below) let you distinguish a
    positive **disconnect** from a transient **outage** — a distinction `0.1.0` could not
    express.
  </Card>
</CardGroup>

**Then `0.2.2` (published 2026-07-04) added revoked-state handling** — a router-signed
§8.3.6 tombstone (top-level `status: "revoked"`) is recognized as a terminal disconnect,
via two **new, additive** surfaces on the `0.2.x` line:

* `gk.endpointState()` → `"active" | "revoked" | "stale" | "absent"`, so you can tell a
  **disconnect** (`"revoked"` → release-to-native, stop polling) from an **outage**
  (`"stale"` → keep enforcing last-known-good, fail-closed).
* an `onRevoked({ childRef, endpointLabel })` config callback, fired once on the
  transition to revoked.

<Note>
  **`isAllowed` returns synchronously; it does not throw for a `block`.** In both `0.1.0`
  and `0.2.2` the signature is `gk.isAllowed({ category, signal? }): Verdict`, and the
  `Verdict` carries `decision: "allow" | "warn" | "block"`. A `block` is a **return
  value**, never an exception — so branch on `verdict.decision`, never a `try/catch`.
  (Verified against the published `@phosra/gatekeeper@0.3.0` type declarations.)
</Note>

### Migrating your code

The upgrade is **behavioral, not a signature break** — the same `isAllowed` call site
compiles on both versions. The work is **adopting the new floor and the new states**.
Both snippets below are self-contained (`declare`d placeholders stand in for the platform
key you mint at endpoint-bind time — see [Link](/sdks/link)):

<CodeGroup>
  ```ts Before — @phosra/gatekeeper@0.1.0 theme={null}
  import { createGatekeeper } from "@phosra/gatekeeper";
  import type { GatekeeperConfig } from "@phosra/gatekeeper";
  import type { SenderKey } from "@openchildsafety/ocss";

  // Your platform's Ed25519 signing key, minted at endpoint-bind time. Never inline it —
  // load from a secret store. See /sdks/link for how to obtain one.
  declare const platformSigningKey: SenderKey;

  // 0.1.0 config: note there is NO profileMaxAgeMs and NO onRevoked in this version.
  const config: GatekeeperConfig = {
    platformDid:          "did:ocss:your-platform",
    platformKeyId:        "did:ocss:your-platform#key-2026",
    gatekeeperSigningKey: platformSigningKey,
    censusBaseUrl:        "https://phosra-api-sandbox-production.up.railway.app",
    trustRootXB64Url:     process.env.PHOSRA_TRUST_ROOT_X!,   // public root pubkey X
    endpointId:           "endpoint-mia-01",                  // the §9.3(b) bound label
    ratingMappings:       [{ ocssCategory: "content_rating", myField: "maturity", vocabulary: "mpaa" }],
  };

  const gk = createGatekeeper(config);

  // isAllowed is synchronous and returns a Verdict; branch on verdict.decision.
  function guard(maturity: string): boolean {
    const verdict = gk.isAllowed({ category: "content_rating", signal: { maturity } });
    return verdict.decision !== "block";
    // 0.1.0 has no endpointState()/onRevoked, so a census outage and a real
    // disconnect are indistinguishable here — the gap 0.2.x closes.
  }
  ```

  ```ts After — @phosra/gatekeeper@0.2.2 theme={null}
  import { createGatekeeper } from "@phosra/gatekeeper";
  import type { GatekeeperConfig } from "@phosra/gatekeeper";
  import type { SenderKey } from "@openchildsafety/ocss";

  declare const platformSigningKey: SenderKey;

  // Your release-to-native handler: drop the tighten-only overlay / "Managed via Phosra"
  // badge. Safe by construction — the overlay only tightens, so lifting it never loosens.
  function releaseToNative(childRef?: string, endpointLabel?: string): void {
    /* remove the OCSS overlay for this (child, endpoint) */
  }

  const config: GatekeeperConfig = {
    platformDid:          "did:ocss:your-platform",
    platformKeyId:        "did:ocss:your-platform#key-2026",
    gatekeeperSigningKey: platformSigningKey,
    censusBaseUrl:        "https://phosra-api-sandbox-production.up.railway.app",
    trustRootXB64Url:     process.env.PHOSRA_TRUST_ROOT_X!,
    endpointId:           "endpoint-mia-01",
    ratingMappings:       [{ ocssCategory: "content_rating", myField: "maturity", vocabulary: "mpaa" }],

    // NEW in 0.2.x — adopt the bounded-staleness floor explicitly (default 60_000ms).
    // A cached profile older than this fail-closed blocks, independent of the census window.
    profileMaxAgeMs: 60_000,

    // NEW in 0.2.2 — fired once on the terminal transition to revoked.
    onRevoked: ({ childRef, endpointLabel }) => releaseToNative(childRef, endpointLabel),
  };

  const gk = createGatekeeper(config);

  // Keep the cached profile fresh INSIDE the floor so a tightened rule always propagates.
  setInterval(() => { void gk.refreshProfile(); }, 30_000); // < profileMaxAgeMs

  function guard(maturity: string): boolean {
    const verdict = gk.isAllowed({ category: "content_rating", signal: { maturity } });
    return verdict.decision !== "block";
  }

  // NEW in 0.2.2 — tell a disconnect ("revoked") apart from an outage ("stale") before
  // releasing. NEVER release on "stale".
  if (gk.endpointState() === "revoked") releaseToNative();
  ```
</CodeGroup>

<Warning>
  **Do not release enforcement on `"stale"`.** `"stale"` means the census is
  unreachable — keep enforcing last-known-good (fail-closed). Only `"revoked"` (a
  positive, signed, terminal disconnect) releases to native controls. Releasing on
  `"stale"` is a fail-open bug.
</Warning>

**Upgrade command:**

```bash npm theme={null}
npm install @phosra/gatekeeper@0.2.2
```

## Worked example — `@phosra/mcp` `0.2.0 → 0.4.0`

The MCP server is run, not imported (`npx @phosra/mcp`), so "upgrading" means bumping
the version your MCP client pins. Between `0.2.0` (2026-05-15) and `0.4.0` (2026-06-28)
the tool surface grew — the published `0.4.0` exposes **79 tools** (federation,
streaming, reversibility) — but the **invocation and auth are unchanged**: same
`phosra-mcp` binary, same `Authorization: Bearer phomcp_…` token.

<CodeGroup>
  ```json Before — client config pinned to 0.2.0 theme={null}
  {
    "mcpServers": {
      "phosra": {
        "command": "npx",
        "args": ["-y", "@phosra/mcp@0.2.0", "--api-key", "phomcp_…"]
      }
    }
  }
  ```

  ```json After — client config pinned to 0.4.0 theme={null}
  {
    "mcpServers": {
      "phosra": {
        "command": "npx",
        "args": ["-y", "@phosra/mcp@0.4.0", "--api-key", "phomcp_…"]
      }
    }
  }
  ```
</CodeGroup>

Confirm the tool count after the bump the same way the [MCP server page](/sdks/mcp-server)
does — a live `tools/list` is authoritative (ignore the cosmetic `Registering 42
tools…` stderr banner):

```bash Verify tool count theme={null}
npx -y @phosra/mcp@0.4.0 --api-key "$PHOSRA_MCP_TOKEN" <<'EOF' | \
  python3 -c "import sys,json; print(len(json.loads(sys.stdin.readlines()[-1])['result']['tools']), 'tools')"
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"1"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
EOF
# → 79 tools
```

<Note>
  New tools are **additive** — every tool your agent already called in `0.2.0` still
  exists in `0.4.0` with the same name and arguments. There is nothing to rewrite; the
  bump only *adds* capability. See the full tool catalog on the
  [MCP server page](/sdks/mcp-server).
</Note>

## Worked example — `@openchildsafety/ocss` `0.1.0 → 0.1.3`

The OCSS reference library is pinned byte-for-byte to the spec's frozen §8.5 golden
vectors, so its `0.1.x` line is **additive within a stable public barrel**: `0.1.1`,
`0.1.2`, and `0.1.3` add and fix subpath exports (`./envelope`, `./trustlist/verify`,
`./profile`, …) without changing what `0.1.0` already exported. A straight patch/minor
bump inside `0.1.x` needs no code change:

```bash npm theme={null}
# Additive within 0.1.x — existing imports keep resolving
npm install @openchildsafety/ocss@0.1.3
```

```ts Unchanged across 0.1.0 → 0.1.3 theme={null}
import { verifyDocument } from "@openchildsafety/ocss/trustlist/verify";
import { seal, open }     from "@openchildsafety/ocss/envelope";
// These imports resolve identically on 0.1.0 and 0.1.3 — the barrel is frozen
// against the production-deployed OCSS verbs; new verbs arrive as new subpaths.
```

<Warning>
  The additive guarantee here holds **within** the `0.1.x` line. Per SemVer §4, a future
  `0.1 → 0.2` minor is the point where a breaking change to the OCSS barrel could land —
  it would ship with its own migration entry in the [changelog](/changelog). Pin exact.
</Warning>

## `@phosra/sdk` — one line so far

`@phosra/sdk` has exactly one published version, **`0.1.0`** — there is no `vX → vY`
migration to write yet. Pin it exactly; when `0.2.0` ships it will arrive here with a
before/after example, and (because it is a `0.x` minor) you should read that entry
before bumping.

```bash npm theme={null}
npm install @phosra/sdk@0.1.0   # the only published version today
```

## Upgrade checklist

Whenever you bump any `@phosra/*` or `@openchildsafety/*` package:

1. **Read the changelog** entry for the target version — [/changelog](/changelog) is the
   dated record; the package's own **README on npm** (`npm view <pkg> readme`, or the
   package page's Readme tab) documents its behavior contract.
2. **Check the bump class.** Patch (`0.2.1 → 0.2.2`) is safe; a `0.x` **minor**
   (`0.1 → 0.2`) may break — treat it like a major.
3. **Pin exact**, install, and let `tsc` surface any `@deprecated` flags or removed
   exports.
4. **Re-run against the sandbox** (`https://phosra-api-sandbox-production.up.railway.app`)
   before touching production — the [Quickstart](/quickstart) path proves your setup
   end-to-end with no API key.
5. **Verify runtime behavior**, not just that it compiles — the gatekeeper example above
   is a compile-clean bump that changes what `isAllowed` returns.

## Next steps

<CardGroup cols={2}>
  <Card title="Versioning (the four layers)" icon="code-branch" href="/versioning">
    How the API path, OCSS spec version, Annex B editions, and SDK semver pin together.
  </Card>

  <Card title="Deprecation & Sunset policy" icon="calendar-xmark" href="/deprecation">
    The advance-notice windows, the `Deprecation` / `Sunset` headers, and how a
    deprecation reaches you.
  </Card>

  <Card title="Changelog" icon="clock-rotate-left" href="/changelog">
    The canonical dated record of every API change, deprecation, and SDK release.
  </Card>

  <Card title="SDK overview" icon="cubes" href="/sdks/overview">
    Every package, its status (published vs. private), and the language-coverage matrix.
  </Card>
</CardGroup>
