From a57ce931acb2a434f140910f252db5b9e8986f3c Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 29 Aug 2026 23:34:45 -0700 Subject: [PATCH 1/4] fix(release): unify component versions at 0.2.0 --- .github/release-notes.md | 57 ++++----- .github/workflows/container-release.yml | 2 + RELEASING.md | 31 ++++- docker/README.md | 10 +- docker/verify-container-release-source.sh | 22 ++++ .../codex-security/.codex-plugin/plugin.json | 2 +- plugins/codex-security/mcp-app/package.json | 2 +- plugins/codex-security/pyproject.toml | 2 +- sdk/typescript/package.json | 6 +- sdk/typescript/scripts/check-package.mjs | 8 ++ sdk/typescript/scripts/smoke-package.mjs | 18 ++- sdk/typescript/scripts/sync-versions.mjs | 78 +++++++++++++ sdk/typescript/src/version.ts | 2 +- .../tests-ts/container-release-source.test.ts | 92 +++++++++++++++ sdk/typescript/tests-ts/sync-versions.test.ts | 109 ++++++++++++++++++ 15 files changed, 394 insertions(+), 47 deletions(-) create mode 100644 docker/verify-container-release-source.sh create mode 100644 sdk/typescript/scripts/sync-versions.mjs create mode 100644 sdk/typescript/tests-ts/container-release-source.test.ts create mode 100644 sdk/typescript/tests-ts/sync-versions.test.ts diff --git a/.github/release-notes.md b/.github/release-notes.md index a0af0767d..1942b4580 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -1,43 +1,30 @@ - + ## Highlights -- Start the preview findings service directly with - `codex-security serve [--port PORT]`, without Docker or an internal package - path. The command reuses the existing service, state, and shutdown behavior; - `--port` overrides `PORT`, and port `0` selects a free port. See - [running without Docker](https://github.com/openai/codex-security/blob/npm-v0.1.24/sdk/typescript/README.md#running-without-docker). -- Observe durable Deep Scan progress from the SDK with the optional - `onDeepProgress({ completed, active, maximum })` callback. Updates report - changed completed and active independent-review counts without blocking the - scan. See - [SDK scan options](https://github.com/openai/codex-security/blob/npm-v0.1.24/sdk/typescript/README.md#sdk-configuration-and-scan-options). -- Make stopped-result handling explicit and stable. Read, list, and export - operations no longer publish late retained results as a side effect; the app - reports when recovery is needed and can recover validated results on request. - See - [stopped result recovery](https://github.com/openai/codex-security/blob/npm-v0.1.24/plugins/codex-security/references/scan-contract.md#stopped-result-recovery). -- Include changed PowerShell `.ps1` files in diff-scan inventories and remove a - conflicting reporting rule so valid internal attack paths remain eligible for - review. Nested Deep Scan workers now also receive an explicitly configured - OpenAI provider credential through the plugin's existing environment boundary. -- Improve Windows reliability by preserving case-insensitive `CODEX_HOME` - entries and repository paths from ordinary PowerShell activity, and by - retrying credential snapshots when a descendant file disappears during ACL - inspection. Existing path-safety and permission failures remain fatal. +- Use one Codex Security release version across the CLI, TypeScript SDK, + bundled plugin, and MCP server. Every product release now advances the plugin + version so cached plugin installations recognize the upgrade. The findings + service and dashboards continue to ship in the same package and image. +- Require container releases to use the same source commit as the matching npm + release tag. An identical package version on a later commit no longer counts + as the same release. +- Recognize BOM-marked UTF-16 source files and PowerShell module and data files + in scan inventories. +- Improve Windows handling of long Codex executable paths, case-only renames, + and dedupe environment settings. ## Upgrade notes -- The findings API and dashboard still have no built-in authentication. - `codex-security serve` binds to loopback by default; keep it local or place it - behind an authenticated TLS proxy before sharing access. Python is still - required, and nonempty imports still require an embeddings API credential. -- Stopped-scan recovery is now explicit. App clients should check - `resultsRecoveryNeeded` and request recovery when they want validated late - results republished. Canceled scans remain immutable and cannot use this - recovery path. -- `onDeepProgress.maximum` is the configured independent-review cap, not a - percentage denominator. The SDK polls the durable projection only when the - callback is supplied. +- SDK consumers using a range such as `^0.1.24` must explicitly update to + `^0.2.0`. Existing CLI commands and public SDK version fields remain available. +- Version alignment does not change artifact schemas or database migration + versions. Saved scans keep their original producer versions, and custom + plugins continue to report their own versions. Codex runtime dependencies and + external plugin catalogs retain their independent versions. +- Publish a container from `container-v0.2.0` at the same commit as + `npm-v0.2.0`. Manual publication from `main` works only while `main` points to + that release commit. See + [container publishing](https://github.com/openai/codex-security/blob/npm-v0.2.0/docker/README.md#publishing). The categorized list below contains the individual changes. diff --git a/.github/workflows/container-release.yml b/.github/workflows/container-release.yml index 61ee8d4a9..ac8921844 100644 --- a/.github/workflows/container-release.yml +++ b/.github/workflows/container-release.yml @@ -291,6 +291,8 @@ jobs: exit 1 fi + sh docker/verify-container-release-source.sh "$version" "$GITHUB_SHA" + printf 'image=ghcr.io/%s\n' "${GITHUB_REPOSITORY_OWNER,,}/codex-security" >> "$GITHUB_OUTPUT" printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT" diff --git a/RELEASING.md b/RELEASING.md index 64a6d82d2..b3d58d63c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,6 +7,19 @@ Historical releases may contain generated notes only. The release tag and npm package use the same stable version: `npm-vX.Y.Z` and `@openai/codex-security@X.Y.Z`. +Starting with `0.2.0`, the CLI, TypeScript SDK, bundled plugin, and MCP server +share one release version. The findings service and dashboards ship in that +same package and container image. `sdk/typescript/package.json` is the version +source; the plugin manifest, private MCP package, and Python test-tooling +metadata are synchronized copies. The Python metadata does not represent a +separate published SDK. + +Keep Codex runtime dependencies, artifact schemas, database migrations, and +test fixture versions independent. Existing public version fields remain +available, and a custom plugin still reports its own producer version. External +Codex catalogs control their own publication versions. Historical releases and +saved scan artifacts retain their original versions. + ## Pull request titles and categories Pull request titles must follow this form: @@ -44,7 +57,11 @@ That manual label takes precedence over the title category. ## Prepare a release 1. Choose the next stable version and update `sdk/typescript/package.json`. - Keep the lockfile version in sync when it records the package version. + Run `pnpm --dir sdk/typescript run sync:versions` to update the component + manifests. Keep the lockfile version in sync when it records the package + version. Every product release advances the bundled plugin version, including + releases without plugin changes, so cached plugin installations refresh on + upgrade. 2. Update `.github/release-notes.md`. Its first line must be `` with the exact package version. 3. Summarize the changes a user will notice. Call out required migration or @@ -56,6 +73,13 @@ That manual label takes precedence over the title category. pull request. Do not merge until required CI, review, and public disclosure checks pass on the current commit. +`pnpm --dir sdk/typescript run check:versions` checks synchronization without +writing files. It also runs during `prepack`, including CI and Docker builds. +Package checks verify that the bundled plugin manifest matches the package; +the installed-package smoke check verifies the public SDK exports, CLI version, +and MCP server version. Do not synchronize dependency versions or rewrite +historical fixtures as part of a release bump. + Review the summary with the same standard as product documentation. Keep it specific, describe behavior before implementation, and do not include private repositories, systems, people, findings, links, or issue identifiers. @@ -81,6 +105,11 @@ do not prepare a release by editing or committing files there. Monitor all three workflows. A version bump is not a completed release until the npm package and GitHub release both exist and match the tag. +Container publication is a separate protected workflow. Create `container-vX.Y.Z` +at the same commit as `npm-vX.Y.Z`, or dispatch from `main` while it still points +to that exact release commit. Matching version strings on different commits are +not sufficient. See [container publishing](docker/README.md#publishing). + ## Verify Check the published state before announcing the release: diff --git a/docker/README.md b/docker/README.md index 50d9ce468..9e4909f5c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -82,9 +82,13 @@ and [package access settings](https://docs.github.com/en/packages/learn-github-p ## Publishing -After merging to `main`, push `container-v` matching the SDK package -version or run `container-release` manually on `main`. Releases require a commit -on protected `main`; pull requests only build and test. +After the npm release tag exists, create `container-v` at the same +commit as `npm-v` and push the container tag. Both tags must match the +SDK package version. You can also run `container-release` manually on `main` +while it still points to that exact npm release commit. If `main` has advanced, +use the matching container tag; a later commit with the same package version is +not the same release. Releases must remain on protected `main` history; pull +requests only build and test. If a release fails, fix the cause and rerun only failed jobs; do not overwrite an existing stable version. `bootstrap` and diff --git a/docker/verify-container-release-source.sh b/docker/verify-container-release-source.sh new file mode 100644 index 000000000..432f45e06 --- /dev/null +++ b/docker/verify-container-release-source.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +set -eu + +if [ "$#" -ne 2 ]; then + printf '%s\n' 'Usage: verify-container-release-source.sh VERSION COMMIT' >&2 + exit 2 +fi + +version=$1 +expected_commit=$2 +release_tag="npm-v$version" + +if ! release_commit="$(git rev-parse --verify "refs/tags/$release_tag^{commit}")"; then + printf '%s\n' "::error::Create the npm release $release_tag before publishing its container." >&2 + exit 1 +fi + +if [ "$release_commit" != "$expected_commit" ]; then + printf '%s\n' "::error::Container version $version must use the same commit as $release_tag. Create container-v$version at that release commit if main has advanced." >&2 + exit 1 +fi diff --git a/plugins/codex-security/.codex-plugin/plugin.json b/plugins/codex-security/.codex-plugin/plugin.json index 72eaf506a..775dfd117 100644 --- a/plugins/codex-security/.codex-plugin/plugin.json +++ b/plugins/codex-security/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "codex-security", - "version": "0.1.79", + "version": "0.2.0", "description": "Codex Security workflows for security scans, analysis, and investigation.", "author": { "name": "OpenAI" diff --git a/plugins/codex-security/mcp-app/package.json b/plugins/codex-security/mcp-app/package.json index c8b6b11b1..89904c68a 100644 --- a/plugins/codex-security/mcp-app/package.json +++ b/plugins/codex-security/mcp-app/package.json @@ -1,6 +1,6 @@ { "name": "codex-security-mcp-app", - "version": "0.1.158", + "version": "0.2.0", "type": "module", "private": true, "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b", diff --git a/plugins/codex-security/pyproject.toml b/plugins/codex-security/pyproject.toml index ae53a7b2b..fb987b8dc 100644 --- a/plugins/codex-security/pyproject.toml +++ b/plugins/codex-security/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-security" -version = "0.1.0" +version = "0.2.0" requires-python = ">=3.12" [project.optional-dependencies] diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index fa937b0c9..d868734b1 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@openai/codex-security", - "version": "0.1.24", + "version": "0.2.0", "description": "TypeScript SDK and CLI for Codex Security", "license": "Apache-2.0", "author": "OpenAI", @@ -48,12 +48,14 @@ "build:plugin": "node scripts/build-plugin.mjs", "check:plugin-source": "node scripts/check-plugin-source.mjs", "check:package": "node scripts/check-package.mjs", + "check:versions": "node scripts/sync-versions.mjs --check", "format": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore \"**/*.{cjs,mjs,js,ts,tsx,json,md}\"", "generate:models": "node scripts/generate-models.cjs", "generate:models:check": "node scripts/generate-models.cjs --check", "lint": "tsc --noEmit", - "prepack": "node --run build:plugin && node --run build", + "prepack": "node --run check:versions && node --run build:plugin && node --run build", "start:server": "node dist/server/index.js", + "sync:versions": "node scripts/sync-versions.mjs", "test": "node --run build:plugin && bun test --timeout 30000 ./tests-ts", "test:ci": "node -e \"require('node:fs').mkdirSync('reports',{recursive:true})\" && pnpm run test --coverage --coverage-reporter=text --coverage-reporter=lcov --reporter=junit --reporter-outfile=reports/junit.xml", "test:mcp": "node --run build:plugin && pnpm --dir ../../plugins/codex-security/mcp-app run test:mcp", diff --git a/sdk/typescript/scripts/check-package.mjs b/sdk/typescript/scripts/check-package.mjs index 333dafd88..e9aea348c 100644 --- a/sdk/typescript/scripts/check-package.mjs +++ b/sdk/typescript/scripts/check-package.mjs @@ -296,6 +296,14 @@ if ( ) { throw new Error("npm package does not contain the expected public metadata."); } +const pluginManifest = JSON.parse( + archiveFile("package/_bundled_plugin/.codex-plugin/plugin.json").toString( + "utf8", + ), +); +if (pluginManifest.version !== packageJson.version) { + throw new Error("Bundled plugin version must match the npm package version."); +} assertExpectedGitHead( packageJson, process.env.CODEX_SECURITY_EXPECTED_GIT_HEAD, diff --git a/sdk/typescript/scripts/smoke-package.mjs b/sdk/typescript/scripts/smoke-package.mjs index 5c8cdc858..922713af3 100644 --- a/sdk/typescript/scripts/smoke-package.mjs +++ b/sdk/typescript/scripts/smoke-package.mjs @@ -247,11 +247,17 @@ async function smokeNestedDeepScanWorker(installedRoot, consumer) { }); } assert.equal(initialized.status, 0, initialized.stderr); + const serverInfo = JSON.parse(initialized.stdout.trim()).result.serverInfo; assert.equal( - JSON.parse(initialized.stdout.trim()).result.serverInfo.name, + serverInfo.name, "codex-security", "The installed MCP launcher must initialize the bundled security server.", ); + assert.equal( + serverInfo.version, + packageManifest.version, + "The installed MCP server must report the package release version.", + ); const globalCodex = spawnSync("codex", ["--version"], { cwd: consumer, @@ -399,7 +405,15 @@ try { [ "--input-type=module", "--eval", - `const sdk = await import(${JSON.stringify(packageManifest.name)}); for (const name of ["CodexSecurity", "publishScan", "publishScanToCustom", "checkScanPublication", "deduplicateScan"]) if (typeof sdk[name] !== "function") throw new Error("The installed package does not export " + name + ".");`, + ` + import assert from "node:assert/strict"; + const sdk = await import(${JSON.stringify(packageManifest.name)}); + for (const name of ["CodexSecurity", "publishScan", "publishScanToCustom", "checkScanPublication", "deduplicateScan"]) { + assert.equal(typeof sdk[name], "function", "The installed package must export " + name + "."); + } + assert.equal(sdk.VERSION, ${JSON.stringify(packageManifest.version)}); + assert.equal(sdk.BUNDLED_PLUGIN_VERSION, sdk.VERSION); + `, ], { cwd: consumer }, ); diff --git a/sdk/typescript/scripts/sync-versions.mjs b/sdk/typescript/scripts/sync-versions.mjs new file mode 100644 index 000000000..98a3f98c9 --- /dev/null +++ b/sdk/typescript/scripts/sync-versions.mjs @@ -0,0 +1,78 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { releaseVersion } from "./release-automation.mjs"; + +const repositoryRoot = fileURLToPath(new URL("../../../", import.meta.url)); +const componentManifests = [ + "plugins/codex-security/.codex-plugin/plugin.json", + "plugins/codex-security/mcp-app/package.json", + "plugins/codex-security/pyproject.toml", +]; + +export async function syncVersions({ + root = repositoryRoot, + check = false, +} = {}) { + const version = releaseVersion( + JSON.parse( + await readFile(join(root, "sdk/typescript/package.json"), "utf8"), + ), + ); + const updates = await Promise.all( + componentManifests.map(async (path) => { + const contents = await readFile(join(root, path), "utf8"); + const field = path.endsWith(".json") + ? /^([ \t]*"version"[ \t]*:[ \t]*")[^"]+("[ \t]*,?)/mu + : /^(version[ \t]*=[ \t]*")[^"]+(")/mu; + if (!field.test(contents)) { + throw new Error(`${path} must declare its component version.`); + } + return { + path, + contents, + updated: contents.replace( + field, + (_match, prefix, suffix) => `${prefix}${version}${suffix}`, + ), + }; + }), + ); + const changed = updates.filter( + ({ contents, updated }) => contents !== updated, + ); + if (check && changed.length > 0) { + throw new Error( + `Component versions must match ${version}:\n${changed.map(({ path }) => path).join("\n")}\nRun pnpm --dir sdk/typescript run sync:versions.`, + ); + } + if (!check) { + for (const { path, updated } of changed) { + await writeFile(join(root, path), updated); + } + } + return { version, changed: changed.map(({ path }) => path) }; +} + +const invokedPath = process.argv[1]; +if ( + invokedPath !== undefined && + pathToFileURL(resolve(invokedPath)).href === import.meta.url +) { + try { + const args = process.argv.slice(2); + if (args.length > 1 || (args.length === 1 && args[0] !== "--check")) { + throw new Error("Usage: node scripts/sync-versions.mjs [--check]"); + } + const check = args[0] === "--check"; + const { version, changed } = await syncVersions({ check }); + console.log( + check + ? `Verified component versions match ${version}.` + : `Synchronized ${changed.length} component manifests to ${version}.`, + ); + } catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exitCode = 1; + } +} diff --git a/sdk/typescript/src/version.ts b/sdk/typescript/src/version.ts index 0fcfbbb6d..bf4a1fca7 100644 --- a/sdk/typescript/src/version.ts +++ b/sdk/typescript/src/version.ts @@ -9,7 +9,7 @@ const PACKAGE_VERSIONS = packageVersions( export const VERSION = PACKAGE_VERSIONS.package; export const CODEX_SDK_VERSION = PACKAGE_VERSIONS.sdk; export const CODEX_EXECUTABLE_VERSION = PACKAGE_VERSIONS.executable; -export const BUNDLED_PLUGIN_VERSION = "0.1.79" as const; +export const BUNDLED_PLUGIN_VERSION = VERSION; const PACKAGE_NAME = "@openai/codex-security"; diff --git a/sdk/typescript/tests-ts/container-release-source.test.ts b/sdk/typescript/tests-ts/container-release-source.test.ts new file mode 100644 index 000000000..9f7a384a3 --- /dev/null +++ b/sdk/typescript/tests-ts/container-release-source.test.ts @@ -0,0 +1,92 @@ +import { readFileSync } from "node:fs"; +import { mkdtemp, realpath, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "bun:test"; +import { bashCommand, runCommand } from "./support/shell.js"; + +const verifier = readFileSync( + new URL( + "../../../docker/verify-container-release-source.sh", + import.meta.url, + ), + "utf8", +); +const roots: string[] = []; + +afterEach(async () => { + for (const root of roots.splice(0)) { + await rm(root, { recursive: true, force: true }); + } +}); + +async function repository() { + const root = await realpath( + await mkdtemp(join(tmpdir(), "container-release-")), + ); + roots.push(root); + async function git(...args: string[]): Promise { + const result = await runCommand( + "git", + [ + "-c", + "user.name=Release Fixture", + "-c", + "user.email=release@example.test", + "-c", + "commit.gpgsign=false", + "-c", + "tag.gpgsign=false", + ...args, + ], + { cwd: root, timeout: 10_000 }, + ); + expect(result.status, result.stderr).toBe(0); + return result.stdout.trim(); + } + await git("init", "--initial-branch=main"); + await writeFile(join(root, "package.json"), '{"version":"0.2.0"}\n'); + await git("add", "package.json"); + await git("commit", "-m", "Release fixture"); + const releaseCommit = await git("rev-parse", "HEAD"); + return { + git, + releaseCommit, + verify: (commit: string) => + runCommand(bashCommand(), ["-s", "--", "0.2.0", commit], { + cwd: root, + input: verifier, + timeout: 10_000, + }), + }; +} + +test.each([false, true])( + "accepts the npm release commit (annotated tag: %s)", + async (annotated) => { + const fixture = await repository(); + await fixture.git( + "tag", + ...(annotated ? ["-a", "-m", "Release fixture"] : []), + "npm-v0.2.0", + ); + const result = await fixture.verify(fixture.releaseCommit); + expect(result.status, result.stderr).toBe(0); + }, +); + +test("rejects a later commit even when its package version is unchanged", async () => { + const fixture = await repository(); + await fixture.git("tag", "npm-v0.2.0"); + await fixture.git("commit", "--allow-empty", "-m", "Later source commit"); + const result = await fixture.verify(await fixture.git("rev-parse", "HEAD")); + expect(result.status).toBe(1); + expect(result.stderr).toContain("same commit as npm-v0.2.0"); +}); + +test("requires the matching npm release tag", async () => { + const fixture = await repository(); + const result = await fixture.verify(fixture.releaseCommit); + expect(result.status).toBe(1); + expect(result.stderr).toContain("npm-v0.2.0 before publishing its container"); +}); diff --git a/sdk/typescript/tests-ts/sync-versions.test.ts b/sdk/typescript/tests-ts/sync-versions.test.ts new file mode 100644 index 000000000..d7b497514 --- /dev/null +++ b/sdk/typescript/tests-ts/sync-versions.test.ts @@ -0,0 +1,109 @@ +import { + mkdir, + mkdtemp, + readFile, + realpath, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { afterEach, expect, test } from "bun:test"; + +const { syncVersions } = (await import( + new URL("../scripts/sync-versions.mjs", import.meta.url).href +)) as { + syncVersions: (options: { + root: string; + check?: boolean; + }) => Promise<{ version: string; changed: string[] }>; +}; + +const roots: string[] = []; +const manifests = [ + "plugins/codex-security/.codex-plugin/plugin.json", + "plugins/codex-security/mcp-app/package.json", + "plugins/codex-security/pyproject.toml", +]; + +afterEach(async () => { + for (const root of roots.splice(0)) { + await rm(root, { recursive: true, force: true }); + } +}); + +async function fixture(): Promise { + const root = await realpath( + await mkdtemp(join(tmpdir(), "release-versions-")), + ); + roots.push(root); + const files: Record = { + "sdk/typescript/package.json": JSON.stringify({ + name: "@openai/codex-security", + version: "0.2.0", + }), + "plugins/codex-security/.codex-plugin/plugin.json": + '{\n "name": "codex-security",\n "version": "0.1.79"\n}\n', + "plugins/codex-security/mcp-app/package.json": JSON.stringify( + { + name: "codex-security-mcp-app", + version: "0.1.158", + private: true, + dependencies: { "@openai/codex-sdk": "0.149.1" }, + }, + null, + 2, + ), + "plugins/codex-security/pyproject.toml": + '[project]\nname = "codex-security"\nversion = "0.1.0"\n\n[tool.ruff]\nrequired-version = "==0.16.1"\n', + }; + for (const [path, contents] of Object.entries(files)) { + await mkdir(dirname(join(root, path)), { recursive: true }); + await writeFile(join(root, path), contents); + } + return root; +} + +test("reports component version drift without rewriting manifests", async () => { + const root = await fixture(); + const before = await Promise.all( + manifests.map((path) => readFile(join(root, path), "utf8")), + ); + + await expect(syncVersions({ root, check: true })).rejects.toThrow( + manifests.join("\n"), + ); + expect( + await Promise.all( + manifests.map((path) => readFile(join(root, path), "utf8")), + ), + ).toEqual(before); +}); + +test("synchronizes component metadata while preserving dependency versions", async () => { + const root = await fixture(); + expect(await syncVersions({ root })).toEqual({ + version: "0.2.0", + changed: manifests, + }); + const plugin = JSON.parse( + await readFile(join(root, manifests[0]!), "utf8"), + ) as { version: string }; + const mcp = JSON.parse(await readFile(join(root, manifests[1]!), "utf8")) as { + version: string; + dependencies: Record; + private: boolean; + }; + expect(plugin.version).toBe("0.2.0"); + expect(mcp.version).toBe("0.2.0"); + expect(mcp.private).toBe(true); + expect(mcp.dependencies["@openai/codex-sdk"]).toBe("0.149.1"); + expect(await readFile(join(root, manifests[2]!), "utf8")).toBe( + '[project]\nname = "codex-security"\nversion = "0.2.0"\n\n[tool.ruff]\nrequired-version = "==0.16.1"\n', + ); + expect(await syncVersions({ root, check: true })).toEqual({ + version: "0.2.0", + changed: [], + }); + expect((await syncVersions({ root })).changed).toEqual([]); +}); From 907037f50596b3238650e3a010d767d03c7cc419 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 29 Aug 2026 23:45:40 -0700 Subject: [PATCH 2/4] test(release): include version checks in the prepack contract --- sdk/typescript/tests-ts/skeleton.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/typescript/tests-ts/skeleton.test.ts b/sdk/typescript/tests-ts/skeleton.test.ts index 27fda31d6..88675d8ca 100644 --- a/sdk/typescript/tests-ts/skeleton.test.ts +++ b/sdk/typescript/tests-ts/skeleton.test.ts @@ -365,7 +365,7 @@ describe("TypeScript package skeleton", () => { "node scripts/check-plugin-source.mjs", ); expect(packageJson.scripts.prepack).toBe( - "node --run build:plugin && node --run build", + "node --run check:versions && node --run build:plugin && node --run build", ); expect(packageJson.scripts.types).not.toContain("check:plugin-source"); expect(packageJson.scripts["audit:prod"]).toBe( From d9aaac4404b02654840d86c2b6172aa66aa37df5 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 30 Aug 2026 00:02:41 -0700 Subject: [PATCH 3/4] chore(evals): align Codex SDK with shipping runtime --- .../skills/triage-finding/evals/package.json | 2 +- .../triage-finding/evals/pnpm-lock.yaml | 66 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/plugins/codex-security/skills/triage-finding/evals/package.json b/plugins/codex-security/skills/triage-finding/evals/package.json index 9ed720c7b..a3300b007 100644 --- a/plugins/codex-security/skills/triage-finding/evals/package.json +++ b/plugins/codex-security/skills/triage-finding/evals/package.json @@ -30,7 +30,7 @@ "pf:view": "PROMPTFOO_CONFIG_DIR=.promptfoo PROMPTFOO_DISABLE_WAL_MODE=true promptfoo view" }, "dependencies": { - "@openai/codex-sdk": "0.137.0", + "@openai/codex-sdk": "0.149.1", "@opencode-ai/sdk": "1.14.33", "promptfoo": "0.121.15" } diff --git a/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml b/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml index 7110bff7f..2346c9b7e 100644 --- a/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml +++ b/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: .: dependencies: '@openai/codex-sdk': - specifier: 0.137.0 - version: 0.137.0 + specifier: 0.149.1 + version: 0.149.1 '@opencode-ai/sdk': specifier: 1.14.33 version: 1.14.33 @@ -990,8 +990,8 @@ packages: resolution: {integrity: sha512-ICKaZ5zrIDg71AiQcsUToVoe5Icmrc3LwSM5+2z7Cf8F1x6nOaY7/ucpFlr4aH8oDe7t3dangc+MsWZTkdvDFw==} engines: {node: '>=18'} - '@openai/codex-sdk@0.137.0': - resolution: {integrity: sha512-KYNY7shsKYOLRNV+kjOoaBaLVHy+FUolHPFraOU4JGeyNR1pORxINTAlwHSxDcuXTibj/3OC6jMGDl7X0q4MUw==} + '@openai/codex-sdk@0.149.1': + resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==} engines: {node: '>=18'} '@openai/codex@0.130.0': @@ -1035,43 +1035,43 @@ packages: cpu: [x64] os: [win32] - '@openai/codex@0.137.0': - resolution: {integrity: sha512-1jUsCnzDBwv7Z4VFZajIlsz41fC18qg6d5qK4PEZhiUk0zJHS90/uGBA70aQPUJLTUZShvyKVAANjw6J/D9eYQ==} + '@openai/codex@0.149.1': + resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==} engines: {node: '>=16'} hasBin: true - '@openai/codex@0.137.0-darwin-arm64': - resolution: {integrity: sha512-YjKmre7DlKslQVhSfocHscgxntZKaZc1LQySKh7q+hNL8jdK+c8nSWSePi583yKFNIxZ8Z/zCkewtjFNvOpQiQ==} + '@openai/codex@0.149.1-darwin-arm64': + resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@openai/codex@0.137.0-darwin-x64': - resolution: {integrity: sha512-zjzrFV80LZby9et44dan82e3cwUd46U7u1LSVXTIz5AUcY4y1KZpAeN6cSLVKMZuOHXTDpi15MUQdRwzdeqIOg==} + '@openai/codex@0.149.1-darwin-x64': + resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@openai/codex@0.137.0-linux-arm64': - resolution: {integrity: sha512-R3ZZymQQA1qpp6OpowN49XJ4scHwSckq7CjVvgmLv3bIs3X+F0XXK3xPFkC9vs2mX3wPekPi3ONpxx+yPAsJ6Q==} + '@openai/codex@0.149.1-linux-arm64': + resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@openai/codex@0.137.0-linux-x64': - resolution: {integrity: sha512-n+26MUj8rekbEDUeYTGoD6HXuGS0MmLHn2LOn0i5qTNYIJvXV82B7cCLSTzVKF/RJxRMRl22se9Q0Z035JIVng==} + '@openai/codex@0.149.1-linux-x64': + resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@openai/codex@0.137.0-win32-arm64': - resolution: {integrity: sha512-Cofktt213TycdQ/v+nAUuwXUBzjMWfA/ZkXyqefyXxDgw0TMtaiM3cgDna3I8YdXnR0PM9AMbx4t7VloJ3ZZYQ==} + '@openai/codex@0.149.1-win32-arm64': + resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==} engines: {node: '>=16'} cpu: [arm64] os: [win32] - '@openai/codex@0.137.0-win32-x64': - resolution: {integrity: sha512-g9qZ9ERrm5OWXMWJOgojYv1kOc5jajTKq37PBMSe56aJfAr9Jk/qBvIOy7LKq3rABdXuz8k+W65PIt2E1hXilw==} + '@openai/codex@0.149.1-win32-x64': + resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -4763,9 +4763,9 @@ snapshots: '@openai/codex': 0.130.0 optional: true - '@openai/codex-sdk@0.137.0': + '@openai/codex-sdk@0.149.1': dependencies: - '@openai/codex': 0.137.0 + '@openai/codex': 0.149.1 '@openai/codex@0.130.0': optionalDependencies: @@ -4795,31 +4795,31 @@ snapshots: '@openai/codex@0.130.0-win32-x64': optional: true - '@openai/codex@0.137.0': + '@openai/codex@0.149.1': optionalDependencies: - '@openai/codex-darwin-arm64': '@openai/codex@0.137.0-darwin-arm64' - '@openai/codex-darwin-x64': '@openai/codex@0.137.0-darwin-x64' - '@openai/codex-linux-arm64': '@openai/codex@0.137.0-linux-arm64' - '@openai/codex-linux-x64': '@openai/codex@0.137.0-linux-x64' - '@openai/codex-win32-arm64': '@openai/codex@0.137.0-win32-arm64' - '@openai/codex-win32-x64': '@openai/codex@0.137.0-win32-x64' + '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64' + '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64' + '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64' + '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64' + '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64' + '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64' - '@openai/codex@0.137.0-darwin-arm64': + '@openai/codex@0.149.1-darwin-arm64': optional: true - '@openai/codex@0.137.0-darwin-x64': + '@openai/codex@0.149.1-darwin-x64': optional: true - '@openai/codex@0.137.0-linux-arm64': + '@openai/codex@0.149.1-linux-arm64': optional: true - '@openai/codex@0.137.0-linux-x64': + '@openai/codex@0.149.1-linux-x64': optional: true - '@openai/codex@0.137.0-win32-arm64': + '@openai/codex@0.149.1-win32-arm64': optional: true - '@openai/codex@0.137.0-win32-x64': + '@openai/codex@0.149.1-win32-x64': optional: true '@opencode-ai/sdk@1.14.33': From 3208788f6c3561562d4ec200a748cbad2321610b Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 30 Aug 2026 06:54:46 -0700 Subject: [PATCH 4/4] Revert "chore(evals): align Codex SDK with shipping runtime" This reverts commit d9aaac4404b02654840d86c2b6172aa66aa37df5. --- .../skills/triage-finding/evals/package.json | 2 +- .../triage-finding/evals/pnpm-lock.yaml | 66 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/plugins/codex-security/skills/triage-finding/evals/package.json b/plugins/codex-security/skills/triage-finding/evals/package.json index a3300b007..9ed720c7b 100644 --- a/plugins/codex-security/skills/triage-finding/evals/package.json +++ b/plugins/codex-security/skills/triage-finding/evals/package.json @@ -30,7 +30,7 @@ "pf:view": "PROMPTFOO_CONFIG_DIR=.promptfoo PROMPTFOO_DISABLE_WAL_MODE=true promptfoo view" }, "dependencies": { - "@openai/codex-sdk": "0.149.1", + "@openai/codex-sdk": "0.137.0", "@opencode-ai/sdk": "1.14.33", "promptfoo": "0.121.15" } diff --git a/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml b/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml index 2346c9b7e..7110bff7f 100644 --- a/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml +++ b/plugins/codex-security/skills/triage-finding/evals/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: .: dependencies: '@openai/codex-sdk': - specifier: 0.149.1 - version: 0.149.1 + specifier: 0.137.0 + version: 0.137.0 '@opencode-ai/sdk': specifier: 1.14.33 version: 1.14.33 @@ -990,8 +990,8 @@ packages: resolution: {integrity: sha512-ICKaZ5zrIDg71AiQcsUToVoe5Icmrc3LwSM5+2z7Cf8F1x6nOaY7/ucpFlr4aH8oDe7t3dangc+MsWZTkdvDFw==} engines: {node: '>=18'} - '@openai/codex-sdk@0.149.1': - resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==} + '@openai/codex-sdk@0.137.0': + resolution: {integrity: sha512-KYNY7shsKYOLRNV+kjOoaBaLVHy+FUolHPFraOU4JGeyNR1pORxINTAlwHSxDcuXTibj/3OC6jMGDl7X0q4MUw==} engines: {node: '>=18'} '@openai/codex@0.130.0': @@ -1035,43 +1035,43 @@ packages: cpu: [x64] os: [win32] - '@openai/codex@0.149.1': - resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==} + '@openai/codex@0.137.0': + resolution: {integrity: sha512-1jUsCnzDBwv7Z4VFZajIlsz41fC18qg6d5qK4PEZhiUk0zJHS90/uGBA70aQPUJLTUZShvyKVAANjw6J/D9eYQ==} engines: {node: '>=16'} hasBin: true - '@openai/codex@0.149.1-darwin-arm64': - resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==} + '@openai/codex@0.137.0-darwin-arm64': + resolution: {integrity: sha512-YjKmre7DlKslQVhSfocHscgxntZKaZc1LQySKh7q+hNL8jdK+c8nSWSePi583yKFNIxZ8Z/zCkewtjFNvOpQiQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@openai/codex@0.149.1-darwin-x64': - resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==} + '@openai/codex@0.137.0-darwin-x64': + resolution: {integrity: sha512-zjzrFV80LZby9et44dan82e3cwUd46U7u1LSVXTIz5AUcY4y1KZpAeN6cSLVKMZuOHXTDpi15MUQdRwzdeqIOg==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@openai/codex@0.149.1-linux-arm64': - resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==} + '@openai/codex@0.137.0-linux-arm64': + resolution: {integrity: sha512-R3ZZymQQA1qpp6OpowN49XJ4scHwSckq7CjVvgmLv3bIs3X+F0XXK3xPFkC9vs2mX3wPekPi3ONpxx+yPAsJ6Q==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@openai/codex@0.149.1-linux-x64': - resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==} + '@openai/codex@0.137.0-linux-x64': + resolution: {integrity: sha512-n+26MUj8rekbEDUeYTGoD6HXuGS0MmLHn2LOn0i5qTNYIJvXV82B7cCLSTzVKF/RJxRMRl22se9Q0Z035JIVng==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@openai/codex@0.149.1-win32-arm64': - resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==} + '@openai/codex@0.137.0-win32-arm64': + resolution: {integrity: sha512-Cofktt213TycdQ/v+nAUuwXUBzjMWfA/ZkXyqefyXxDgw0TMtaiM3cgDna3I8YdXnR0PM9AMbx4t7VloJ3ZZYQ==} engines: {node: '>=16'} cpu: [arm64] os: [win32] - '@openai/codex@0.149.1-win32-x64': - resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==} + '@openai/codex@0.137.0-win32-x64': + resolution: {integrity: sha512-g9qZ9ERrm5OWXMWJOgojYv1kOc5jajTKq37PBMSe56aJfAr9Jk/qBvIOy7LKq3rABdXuz8k+W65PIt2E1hXilw==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -4763,9 +4763,9 @@ snapshots: '@openai/codex': 0.130.0 optional: true - '@openai/codex-sdk@0.149.1': + '@openai/codex-sdk@0.137.0': dependencies: - '@openai/codex': 0.149.1 + '@openai/codex': 0.137.0 '@openai/codex@0.130.0': optionalDependencies: @@ -4795,31 +4795,31 @@ snapshots: '@openai/codex@0.130.0-win32-x64': optional: true - '@openai/codex@0.149.1': + '@openai/codex@0.137.0': optionalDependencies: - '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64' - '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64' - '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64' - '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64' - '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64' - '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64' + '@openai/codex-darwin-arm64': '@openai/codex@0.137.0-darwin-arm64' + '@openai/codex-darwin-x64': '@openai/codex@0.137.0-darwin-x64' + '@openai/codex-linux-arm64': '@openai/codex@0.137.0-linux-arm64' + '@openai/codex-linux-x64': '@openai/codex@0.137.0-linux-x64' + '@openai/codex-win32-arm64': '@openai/codex@0.137.0-win32-arm64' + '@openai/codex-win32-x64': '@openai/codex@0.137.0-win32-x64' - '@openai/codex@0.149.1-darwin-arm64': + '@openai/codex@0.137.0-darwin-arm64': optional: true - '@openai/codex@0.149.1-darwin-x64': + '@openai/codex@0.137.0-darwin-x64': optional: true - '@openai/codex@0.149.1-linux-arm64': + '@openai/codex@0.137.0-linux-arm64': optional: true - '@openai/codex@0.149.1-linux-x64': + '@openai/codex@0.137.0-linux-x64': optional: true - '@openai/codex@0.149.1-win32-arm64': + '@openai/codex@0.137.0-win32-arm64': optional: true - '@openai/codex@0.149.1-win32-x64': + '@openai/codex@0.137.0-win32-x64': optional: true '@opencode-ai/sdk@1.14.33':