diff --git a/bun.lock b/bun.lock index 966b29f0..85f5eba1 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,7 @@ }, "devDependencies": { "@biomejs/biome": "2.5.14", - "@opencode-ai/plugin": "1.18.6", + "@opencode-ai/plugin": "1.18.31", "@types/bun": "1.4.2", "@types/node": "26.6.2", "typescript": "7.0.2", @@ -56,9 +56,9 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.6", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.6", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-xqHIkmXhAOjw8UJYW4s7iiW6P/eoEuU8tKf13YBstdXqH46rTxPvsD+KMw8cSZ/PVF0bdrrVsj58l0j3xpW3Qg=="], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.31", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-Rdc1bPK06PByaGyGd0kf7JUZ4pTkexz2OOUNlqZWLpHOiMEZ+/rFGjt46ypZ3QwA697gNdWwwwQbHbKG5NMwGA=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.6", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-bK2rca3tYOo1h2tWNaO+lUiL+qZ9wgE/HcM3N46FahykYwL5RL1r2G6GEVyV3BBv5Qs0g0+yIlosBWJWwF13AQ=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.31", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Raouthf8Lhe9edjvYeeSK7SgvdoU6bBjH9qV3f70dHoa6h+z0X2TMz/e22/wKp/StlFUZ4kIRpYYxFnY8/k01w=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], diff --git a/docs/release-qualification.md b/docs/release-qualification.md index 6f0999ee..95a47320 100644 --- a/docs/release-qualification.md +++ b/docs/release-qualification.md @@ -104,7 +104,7 @@ Authorize dispatches using the [paid-run budget](../.agents/plans/05-release-sim Keep that ledger across retries. Budget-stopped campaigns cannot qualify. ```bash -bun run eval -- --release --model openai/gpt-5.6-sol --model xai/grok-4.6 +bun run eval -- --release --model openai/gpt-6-sol --model xai/grok-4.6 bun run eval:canary -- prepare --report /report.json --out # Run the prepared fixture, then record its session and transcript. bun run eval:canary -- record diff --git a/evals/release-policy.ts b/evals/release-policy.ts index af9daede..1484c1a9 100644 --- a/evals/release-policy.ts +++ b/evals/release-policy.ts @@ -108,7 +108,7 @@ export const RELEASE_MAX_CAMPAIGN_AGE_MS = 7 * 24 * 60 * 60 * 1_000; export const RELEASE_ENVIRONMENT_RESERVES_PER_STRATUM = 1; export const RELEASE_HOST_POLICY = { - opencodeVersion: "1.18.6", + opencodeVersion: "1.18.31", platform: "linux", reviewerSteps: null, } as const; diff --git a/package.json b/package.json index 1de23dad..64e6bb55 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ }, "devDependencies": { "@biomejs/biome": "2.5.14", - "@opencode-ai/plugin": "1.18.6", + "@opencode-ai/plugin": "1.18.31", "@types/bun": "1.4.2", "@types/node": "26.6.2", "typescript": "7.0.2" diff --git a/scripts/eval-canary.ts b/scripts/eval-canary.ts index 84493f7e..662147d8 100644 --- a/scripts/eval-canary.ts +++ b/scripts/eval-canary.ts @@ -23,6 +23,7 @@ import { packedPackageManifest, samePackedArtifact, } from "../evals/provenance.js"; +import { RELEASE_HOST_POLICY } from "../evals/release-policy.js"; import type { ActorIdentity, ArtifactIdentity } from "../evals/report.js"; import { reportArtifactForCanary } from "../evals/report-artifact.js"; import { assuranceProjection } from "../src/application/delivery.js"; @@ -519,6 +520,7 @@ export function deriveCanaryResult(input: { "loads-flow-tools": host.preparedFixture && host.versions.length === 1 && + host.versions[0] === RELEASE_HOST_POLICY.opencodeVersion && hasCompletedFlowCall && loadedPlugin, "saves-plan": diff --git a/tests/eval-canary.test.ts b/tests/eval-canary.test.ts index 100afee2..8a6f246b 100644 --- a/tests/eval-canary.test.ts +++ b/tests/eval-canary.test.ts @@ -11,6 +11,7 @@ import { } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { RELEASE_HOST_POLICY } from "../evals/release-policy.js"; import { artifactIdentitySha256, CANARY_CHECKLIST_SHA256, @@ -146,7 +147,7 @@ function canaryTranscript( }, })); return { - info: { directory, version: "1.18.6" }, + info: { directory, version: RELEASE_HOST_POLICY.opencodeVersion }, messages: [ { info: { @@ -377,6 +378,27 @@ describe("canary record boundary", () => { ]); }); + test("rejects a canary recorded on a different OpenCode release host", () => { + const value = prepared(); + const transcript = canaryTranscript(); + const oldHostTranscript = { + ...transcript, + info: { ...transcript.info, version: "1.18.6" }, + }; + const derived = deriveCanaryResult({ + packageVersion: value.artifact.packageVersion, + artifactSha256: value.artifactSha256, + tarballSha256: value.artifact.tarballSha256, + preparedSha256: value.sha256, + pluginEntrySha256: value.pluginEntrySha256, + installation: installation(value), + session: canarySession(), + transcript: oldHostTranscript, + }); + expect(derived.checks["loads-flow-tools"]).toBe(false); + expect(derived.status).toBe("failed"); + }); + test("refuses empty validation and delivery assertion sets", () => { const value = prepared(); const session = structuredClone(canarySession()) as unknown as { @@ -727,8 +749,6 @@ describe("canary release verification", () => { now: new Date(now), ...(freshness ? { freshness } : {}), }); - // Retained evidence is read exactly as it would have been read inside its - // window: the expiry stops applying, and nothing else does. expect(await verify("2026-08-29T00:00:00.000Z", "retained")).toBe( await verify("2026-08-25T01:00:00.000Z"), ); diff --git a/tests/live-opencode-smoke.test.ts b/tests/live-opencode-smoke.test.ts index 53a0d710..6dd1f9c3 100644 --- a/tests/live-opencode-smoke.test.ts +++ b/tests/live-opencode-smoke.test.ts @@ -333,8 +333,8 @@ function evidence(overrides: Partial = {}) { } describe("OpenCode eval metadata probe", () => { - test("pins the Phase 0 host, endpoints, and reviewer bound", () => { - expect(HOST_METADATA_CONTRACT.hostVersion).toBe("1.18.6"); + test("pins the current host, endpoints, and reviewer bound", () => { + expect(HOST_METADATA_CONTRACT.hostVersion).toBe("1.18.31"); expect(HOST_METADATA_CONTRACT.endpoints).toEqual({ agents: "GET /agent", createSession: "POST /session", diff --git a/tests/qualification-cli.test.ts b/tests/qualification-cli.test.ts index 953480a7..98217a89 100644 --- a/tests/qualification-cli.test.ts +++ b/tests/qualification-cli.test.ts @@ -30,6 +30,7 @@ import { } from "../evals/qualification-bundle.js"; import { regradeQualificationBundle } from "../evals/qualification-regrade.js"; import { + RELEASE_HOST_POLICY, releaseCatalog, releaseGraderBundle, releaseHostConfigSha256, @@ -142,7 +143,10 @@ function canaryTranscript(input: { state: { status: "completed", input: {}, output }, }); return { - info: { directory: input.fixture, version: "1.18.6" }, + info: { + directory: input.fixture, + version: RELEASE_HOST_POLICY.opencodeVersion, + }, messages: [ { info: { @@ -225,7 +229,7 @@ test("qualifies and seals a complete exact-artifact campaign through the CLI", a models, scenarios, sampling: { kind: "release" }, - opencodeVersion: "1.18.6", + opencodeVersion: RELEASE_HOST_POLICY.opencodeVersion, }); const evaluator = evaluatorIdentity({ sourceCommit: artifact.sourceCommit, @@ -437,6 +441,48 @@ test("qualifies and seals a complete exact-artifact campaign through the CLI", a recordedAt, }); expect(canary.record.status).toBe("passed"); + const currentHostTranscript = canaryTranscript({ + fixture: join(preparedDirectory, "fixture"), + packageVersion: artifact.packageVersion, + pluginEntrySha256: prepared.pluginEntrySha256, + session, + }); + const oldHostTranscript = { + ...currentHostTranscript, + info: { ...currentHostTranscript.info, version: "1.18.6" }, + }; + const oldHostCanary = await recordCanary({ + repositoryRoot: join(temporary, "old-host-canary-root"), + prepared, + preparedDirectory, + operator: "qualification-test", + session, + transcript: oldHostTranscript, + recordedAt, + }); + expect(oldHostCanary.record.status).toBe("failed"); + const rejected = Bun.spawn( + [ + "bun", + "run", + "qualify", + "--", + "--campaign-dir", + campaignDirectory, + "--canary", + oldHostCanary.path, + ], + { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }, + ); + const [rejectedStdout, rejectedStderr, rejectedExitCode] = + await Promise.all([ + new Response(rejected.stdout).text(), + new Response(rejected.stderr).text(), + rejected.exited, + ]); + expect(rejectedExitCode).not.toBe(0); + expect(rejectedStdout).not.toContain("VERIFIED:"); + expect(rejectedStderr).toContain("Canary status is failed."); const bundlesDirectory = join(temporary, "bundles"); const qualified = Bun.spawn( diff --git a/tests/release-metadata.test.ts b/tests/release-metadata.test.ts index 8d158fba..934afe91 100644 --- a/tests/release-metadata.test.ts +++ b/tests/release-metadata.test.ts @@ -99,7 +99,7 @@ const canarySession = (() => { return SessionSchema.parse(session); })(); const canaryTranscript = (packageVersion: string) => ({ - info: { directory: "", version: "1.18.6" }, + info: { directory: "", version: "1.18.31" }, messages: [ { info: { diff --git a/tests/release-qualification.test.ts b/tests/release-qualification.test.ts index 8d8c8ea4..1d74f34d 100644 --- a/tests/release-qualification.test.ts +++ b/tests/release-qualification.test.ts @@ -45,7 +45,7 @@ function releaseReport(stopped = false) { models: MODELS, scenarios, sampling: { kind: "release" }, - opencodeVersion: "1.18.6", + opencodeVersion: "1.18.31", }); const evaluator = evaluatorIdentity({ sourceCommit: ARTIFACT.sourceCommit,