Versioning
Phosra pins versions at four layers — the API path, the OCSS spec version, dated Annex B editions, and the SDK semver. Each is stable and additive, so code you ship today keeps working.
Runnable. Every version string and status below was captured live from
https://phosra-api-sandbox-production.up.railway.app on 2026-07-06.
The four layers
| Layer | Where you see it | Today's value | Changes when |
|---|---|---|---|
| API version | URL path /api/v1/… | v1 | A breaking API change → a new path (/api/v2); v1 keeps serving |
| OCSS spec version | ocss_version in the trust list; OCSS-Spec-Version request header | OCSS-v1.0-pre | The protocol itself revs |
| Annex B edition | /api/v1/annex-b/editions/{n} | (pre-Edition-1) | The steward freezes a new numbered edition |
| SDK | your package.json | semver, e.g. @phosra/[email protected] | Normal library releases; pin exact |
API version — it is in the path
The major version is the first path segment. Everything under /api/v1 shares one
contract, and additions inside v1 are backward-compatible. A future breaking
change gets a new path — v1 is never mutated under you.
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)What "backward-compatible additions" means inside v1: we may add new
endpoints, new optional request fields, and new response fields or enum
values. Write clients that ignore unknown fields and do not break on a new
enum value — that is the whole forward-compatibility contract.
OCSS spec version
The protocol carried over the API has its own version, OCSS-v1.0-pre. You can read
the census's binding of it directly from the (unmetered, unauthenticated) trust list:
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s "$BASE/.well-known/ocss/trust-list" \
| python3 -c "import sys,json; d=json.loads(json.load(sys.stdin)['document']); \
print('ocss_version =', d['ocss_version']); print('schema_version =', d['schema_version'])"ocss_version = OCSS-v1.0-pre
schema_version = 1On signed census writes, your client declares which spec it built against by
setting the OCSS-Spec-Version request header (covered by your RFC 9421 signature).
Send OCSS-v1.0-pre; the census pins its own side via ocss_version, so a mismatch
is detectable rather than silent.
OCSS-Spec-Version is a request header you set on signed census calls — it is
not echoed on ordinary product-plane responses. Do not poll for it on a GET;
read ocss_version from the trust list instead.
Dated artifacts — Annex B editions
Governed content (the rule registry, statute mappings) is published as frozen,
numbered Annex B editions. An edition is content-addressed and immutable: once
edition N is cut, its bytes never change, and its ETag is the artifact hash. Pin
to an edition number and you have pinned to exact bytes forever.
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s -o /dev/null -w "edition 1 → %{http_code}\n" "$BASE/api/v1/annex-b/editions/1"
# → edition 1 → 404 (the sandbox census is pre-Edition-1; no editions frozen yet)A 404 on an edition number means that edition is not yet frozen — every
number returns 404 before Edition 1 is cut. It is a clean not-found, never a
substitution to a nearby edition. Rule slugs reference their registry edition
inline as ocss-rules/<edition>#<slug>, so a stored rule always names the exact
edition it was written against.
Conditional reads and caching
Discovery artifacts (the trust list, profiles, editions) return a strong ETag and
Cache-Control: public, max-age=300. Send the ETag back as If-None-Match to get
an empty 304 when nothing moved — free, and it does not count against your
rate limit.
BASE=https://phosra-api-sandbox-production.up.railway.app
# 1. First read — capture the ETag
ETAG=$(curl -s -D - -o /dev/null "$BASE/.well-known/ocss/trust-list" \
| tr -d '\r' | awk 'tolower($1)=="etag:"{print $2}')
# 2. Conditional read — 304 while unchanged, 200 + body when it moves
curl -s -o /dev/null -w "%{http_code}\n" \
-H "If-None-Match: $ETAG" "$BASE/.well-known/ocss/trust-list"
# → 304The trust list also carries a monotonic issue serial so you can detect a change
without diffing the body — a higher issue.number means the census re-issued:
curl -s "$BASE/.well-known/ocss/trust-list" \
| python3 -c "import sys,json; i=json.loads(json.load(sys.stdin)['document'])['issue']; \
print('issue #', i['number'], 'at', i['issued_at'])"
# e.g. → issue # 136 at 2026-07-06T06:44:43ZSDK versioning — pin exact
The published SDKs follow semver. Pin the exact version you tested against and bump deliberately; do not float on a caret range in production.
| Package | Latest | Pin |
|---|---|---|
@phosra/sdk | 0.1.0 | "@phosra/sdk": "0.1.0" |
@phosra/link | 0.6.1 | "@phosra/link": "0.6.1" |
@phosra/mcp | 0.4.0 | "@phosra/mcp": "0.4.0" |
@openchildsafety/ocss (protocol/signing) | 0.1.5 | "@openchildsafety/ocss": "0.1.5" |
These packages are pre-1.0 (0.x). Under semver, a 0.x minor bump may carry
breaking changes, so an exact pin — not ^0.1.0 — is the safe default until a
1.0.0 line ships. Read the changelog before you bump.
# Pin exact — reproducible installs, no surprise breaks
npm install @phosra/[email protected] @openchildsafety/[email protected]Staying forward-compatible
Ignore unknown fields
New response fields and enum values arrive inside v1. Deserialize leniently;
never fail on a field you did not expect.
Pin dated artifacts by number
Reference Annex B editions by number and verify the ETag. Frozen bytes never
move under you.
Pin the SDK exact
Test against a specific version, pin it, and bump on purpose after reading the changelog.
Watch the issue serial
Poll the trust list with If-None-Match; act only when issue.number climbs.
Next steps
Deprecation & sunset policy
The lifecycle before anything can change — advance-notice windows, the
Deprecation/Sunset headers, and how to migrate.
Changelog
What shipped, when — including SDK releases and platform conventions.
Forward compatibility (OCSS)
How the protocol itself stays additive across editions.