diff --git a/README.md b/README.md index 497bfd9..f093623 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ npm i @openmaxai/openmax-agent-sdk > resolves it via the `latest` dist-tag. Pre-1.0 alphas remain available under the > `alpha` dist-tag for history. +> Cutting a new version? See **[`RELEASING.md`](./RELEASING.md)** for the exact +> files to bump and the release flow. + **In the SDK** (generic CWS + agent-level concerns): - **transport/** — `WsClient` (auth, heartbeat, client keepalive-ping + frame-watchdog, exponential-backoff reconnect, 4001–4006 close-code handling), HTTP client (native `fetch` + auth), CF-Access headers, token/identity management. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..72c70ba --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,70 @@ +# Releasing / bumping the SDK version + +This is the checklist for cutting a new version of +`@openmaxai/openmax-agent-sdk`. Follow it exactly — the version string lives in +more than one place on purpose, and CI will fail if they drift. + +## 1. Files to change on every version bump + +| File | What to change | Enforced by | +| --- | --- | --- | +| `package.json` | `version` field | `release.yml` refuses to publish unless the git tag matches this | +| `package-lock.json` | root `version` **and** `packages[""].version` — just run `npm install` to regenerate both | — | +| `src/index.js` | the `SDK_VERSION` **literal** — must equal `package.json` `version` | `src/version.test.js` (CI fails on any mismatch) | +| `README.md` | the "first stable release" note near the top and the release-summary paragraph near the bottom, **if** the human-facing notes change | — (prose; not machine-checked) | + +That's the whole set. A single `npm install` after editing `package.json` +handles the lockfile; the other two are hand edits. + +## 2. Do NOT + +- **Do not turn `SDK_VERSION` back into a runtime read of `package.json`** + (e.g. `createRequire(import.meta.url)('../package.json').version`). It reads + cleanly when the SDK runs from its own installed package, but it does **not + survive bundling**: a consumer that inlines the SDK into a self-contained + artifact carries the read into their bundle, where `../package.json` fails to + resolve at load time. That regression is exactly why `SDK_VERSION` is a + literal today. Keep it a literal; `src/version.test.js` is the drift guard so + you get the anti-drift guarantee without the runtime read. +- **Do not touch these — they are not the package version:** + - `src/orchestrator.js` `reporters.version` (a reporter payload default, + supplied by the host adapter). + - `CONTRACT.md` / `schemas/v1/**` `version` fields — the protocol contract is + versioned **independently** of the npm package. + +## 3. Release steps + +1. Branch off `main`; bump the files in §1. +2. Verify locally: `npm test` (must be green, incl. the `SDK_VERSION` drift + guard) and — if you touched anything the scanners see — the same Semgrep + command CI runs. +3. Push, open a PR. **Required CI must pass**: `test (node 20)`, + `test (node 22)`, `semgrep`, `gitleaks`. Get an approval from **someone + other than the last pusher** (org branch ruleset). Merge to `main`. +4. Tag the merged commit and push the tag: + ```bash + git tag -a vX.Y.Z -m "vX.Y.Z" + git push origin vX.Y.Z + ``` + The tag **must** match `package.json` `version` (release.yml checks this) and + point at a commit contained in protected `main` (release.yml checks this too + — an unmerged commit is refused). +5. Pushing `v*` triggers **`release.yml`**, which pauses at the `release` + environment approval gate. A reviewer approves the deployment, then it runs + `npm publish --provenance`. +6. **dist-tag is automatic** from the version shape: + - stable (no hyphen, e.g. `1.0.1`) → published to **`latest`**; + - prerelease (a hyphen, e.g. `1.1.0-alpha.0`) → published to **`alpha`**, + and `latest` is left untouched (a stable already exists). + +## 4. Fixing a dist-tag after the fact + +Use the **`promote-dist-tag`** workflow (Actions → Run workflow). It moves a +dist-tag (e.g. `latest`) onto an already-published version from CI, gated by the +same `release` environment — no local `npm login` needed. It refuses to point a +tag at a version that was never published. + +## 5. Versioning scheme + +Semver. Prereleases use `-alpha.N`. Patch = fixes (incl. behavior fixes for +consumers, like a bundling fix); minor = additive API; major = breaking changes. diff --git a/package-lock.json b/package-lock.json index 8fcc657..0e64660 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@openmaxai/openmax-agent-sdk", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@openmaxai/openmax-agent-sdk", - "version": "1.0.0", + "version": "1.0.1", "license": "UNLICENSED", "dependencies": { "ws": "^8.21.0" diff --git a/package.json b/package.json index a5cacd5..802fb23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openmaxai/openmax-agent-sdk", - "version": "1.0.0", + "version": "1.0.1", "description": "CWS agent runtime SDK — cws-comm protocol layer (WS/auth/heartbeat/reconnect, sync, message codec, tm/kb/as/comm/core/conn service clients) extracted from zylos-openmax; consumed by runtime adapters.", "type": "module", "main": "src/index.js", diff --git a/src/index.js b/src/index.js index 505cc33..8be8103 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,6 @@ * Scaffold. Modules are re-exported here as they are extracted from * zylos-openmax (Phase A). Current tranche: providers. */ -import { createRequire } from 'node:module'; - export * from './providers.js'; // ── transport layer (Phase A · milestone 1) ───────────────────────────────── @@ -70,5 +68,10 @@ export * from './identity/self-name-hydration.js'; // createSelfNameHydrator // CLI shells) stays in the adapter behind the injected providers/callbacks. export * from './orchestrator.js'; // CwsAgentBridge -// Sourced from package.json so it never drifts from the released version. -export const SDK_VERSION = createRequire(import.meta.url)('../package.json').version; +// Hardcoded literal, NOT a runtime read of package.json. A `createRequire(...) +// ('../package.json')` here does not survive bundling: when a consumer inlines +// the SDK into a self-contained artifact, that call is preserved and resolves +// `../package.json` relative to the CONSUMER's bundle at runtime — which fails +// when the bundle is loaded in isolation. The `SDK_VERSION` test asserts this +// literal stays in sync with package.json, so it can never silently drift. +export const SDK_VERSION = '1.0.1'; diff --git a/src/version.test.js b/src/version.test.js new file mode 100644 index 0000000..bac3be9 --- /dev/null +++ b/src/version.test.js @@ -0,0 +1,15 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { SDK_VERSION } from './index.js'; + +// `SDK_VERSION` is a hardcoded literal in src/index.js — deliberately NOT a +// runtime `createRequire('../package.json')`, because that call does not survive +// bundling: a consumer inlining the SDK into a self-contained artifact would +// carry the read into their bundle, where `../package.json` fails to resolve at +// runtime. This test is the drift guard — reading package.json here is safe +// because tests run in-repo (package.json present) and are never bundled. +test('SDK_VERSION matches package.json version', () => { + const pkg = createRequire(import.meta.url)('../package.json'); + assert.equal(SDK_VERSION, pkg.version); +});