fix(release): stabilize beta validation and completion (#114396)

* test(release): fix rebased validation gates

* test(migrate-hermes): use canonical auth store fixture

* test(secrets): reset runtime state before migration isolation

* test(secrets): reload singleton graph for migration isolation

* test(secrets): isolate auth migration state

* fix(release): return completed validation run

* docs: refresh docs map after forward-port
This commit is contained in:
Peter Steinberger
2026-07-27 03:42:00 -04:00
committed by GitHub
parent d4ed57bc88
commit 3c67fcc45d
6 changed files with 117 additions and 87 deletions

View File

@@ -707,17 +707,24 @@ describe("Hermes migration secret items", () => {
const stateDir = path.join(root, "state");
const agentDir = path.join(stateDir, "agents", "main", "agent");
await writeFile(path.join(source, ".env"), "OPENAI_API_KEY=sk-hermes\n");
await writeFile(
path.join(agentDir, "auth.json"),
JSON.stringify({
openai: { type: "api_key", provider: "openai", key: "legacy-main-key" },
}),
);
const existingStore: AuthProfileStore = {
version: 1,
profiles: {
"openai:existing": {
type: "api_key",
provider: "openai",
key: "existing-main-key",
},
},
};
writeAuthProfileStore(agentDir, existingStore);
const beforePlanStore = readAuthProfileStore(agentDir);
const provider = buildHermesMigrationProvider();
await provider.plan(makeContext({ source, stateDir, workspaceDir, includeSecrets: true }));
await expect(fs.access(path.join(agentDir, "auth.json"))).resolves.toBeUndefined();
expect(readAuthProfileStore(agentDir)).toEqual(beforePlanStore);
// Canonical profiles stay in SQLite; planning must not recreate the retired JSON sidecar.
await expectMissingPath(path.join(agentDir, "auth-profiles.json"));
});

View File

@@ -2,6 +2,10 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
clearRuntimeAuthProfileStoreSnapshots,
saveAuthProfileStore,
} from "openclaw/plugin-sdk/agent-runtime";
import {
getProviderHttpMocks,
installProviderHttpMockCleanup,
@@ -130,9 +134,8 @@ describe("openai video generation provider", () => {
const previousOpenAIKey = process.env.OPENAI_API_KEY;
delete process.env.OPENAI_API_KEY;
try {
fs.writeFileSync(
path.join(agentDir, "auth-profiles.json"),
JSON.stringify({
saveAuthProfileStore(
{
version: 1,
profiles: {
"openai:chatgpt": {
@@ -143,11 +146,14 @@ describe("openai video generation provider", () => {
expires: Date.now() + 60_000,
},
},
}),
},
agentDir,
{ filterExternalAuthProfiles: false, syncExternalCli: false },
);
expect(buildOpenAIVideoGenerationProvider().isConfigured?.({ agentDir })).toBe(false);
} finally {
clearRuntimeAuthProfileStoreSnapshots();
if (previousOpenAIKey === undefined) {
delete process.env.OPENAI_API_KEY;
} else {

View File

@@ -17,6 +17,7 @@ import {
} from "./test-helpers.js";
type XaiStreamApi = Extract<Api, "openai-completions" | "openai-responses">;
type StreamEvent = Record<string, unknown> & { type?: string };
const PAYLOAD_CAPTURE_TIMEOUT_MS = 5_000;
async function collectEvents(stream: ReturnType<StreamFn>): Promise<StreamEvent[]> {
const events: StreamEvent[] = [];
@@ -124,7 +125,7 @@ async function captureXaiResponsesPayloadWithThinking(
const payloadPromise = new Promise<Record<string, unknown>>((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error("provider payload callback was not invoked")),
1_000,
PAYLOAD_CAPTURE_TIMEOUT_MS,
);
const stream = streamSimple(
model,
@@ -478,19 +479,19 @@ describe("xai stream wrappers", () => {
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
});
}, 10_000);
it("clamps unsupported Grok 4.5 off reasoning to low", async () => {
const payload = await captureXaiResponsesPayloadWithThinking("off");
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
});
}, 10_000);
it("maps Grok 4.3 off reasoning to xAI none", async () => {
const payload = await captureXaiResponsesPayloadWithThinking("off", "grok-4.3");
expect(payload.reasoning).toEqual({ effort: "none" });
});
}, 10_000);
it("moves image-bearing tool results out of function_call_output payloads", () => {
const payload: Record<string, unknown> = {

View File

@@ -299,7 +299,7 @@ function waitForWorkflowRun(parentRunId, workflowSha) {
}
if (suite?.status === "completed") {
if (suite.conclusion === "success") {
return;
return suite;
}
throw new Error(
`Full Release Validation concluded ${String(suite.conclusion).toLowerCase()}: https://github.com/openclaw/openclaw/actions/runs/${parentRunId}`,

View File

@@ -1,85 +1,100 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { writePersistedAuthProfileStoreRaw } from "../agents/auth-profiles/sqlite.js";
import { resolveApiKeyForProvider } from "../agents/model-auth.js";
import { closeOpenClawAgentDatabasesForTest } from "../state/openclaw-agent-db.js";
import {
activateSecretsRuntimeSnapshot,
clearSecretsRuntimeSnapshot,
prepareSecretsRuntimeSnapshot,
} from "./runtime.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let resolveApiKeyForProvider: typeof import("../agents/model-auth.js").resolveApiKeyForProvider;
let closeOpenClawAgentDatabasesForTest: typeof import("../state/openclaw-agent-db.js").closeOpenClawAgentDatabasesForTest;
let withOpenClawTestState: typeof import("../test-utils/openclaw-test-state.js").withOpenClawTestState;
let activateSecretsRuntimeSnapshot: typeof import("./runtime.js").activateSecretsRuntimeSnapshot;
let clearSecretsRuntimeSnapshot: typeof import("./runtime.js").clearSecretsRuntimeSnapshot;
let prepareSecretsRuntimeSnapshot: typeof import("./runtime.js").prepareSecretsRuntimeSnapshot;
describe("auth profile migration isolation", () => {
const roots: string[] = [];
afterEach(async () => {
beforeEach(async () => {
// This shard is non-isolated, so load the singleton-backed runtime and auth helpers
// from one fresh graph after neighboring tests reset the module cache.
vi.resetModules();
({
activateSecretsRuntimeSnapshot,
clearSecretsRuntimeSnapshot,
prepareSecretsRuntimeSnapshot,
} = await import("./runtime.js"));
({ resolveApiKeyForProvider } = await import("../agents/model-auth.js"));
({ closeOpenClawAgentDatabasesForTest } = await import("../state/openclaw-agent-db.js"));
({ withOpenClawTestState } = await import("../test-utils/openclaw-test-state.js"));
clearSecretsRuntimeSnapshot();
closeOpenClawAgentDatabasesForTest();
vi.unstubAllEnvs();
});
afterEach(() => {
clearSecretsRuntimeSnapshot();
closeOpenClawAgentDatabasesForTest();
vi.unstubAllEnvs();
for (const root of roots.splice(0)) {
await fs.rm(root, { recursive: true, force: true });
}
});
it("isolates one legacy owner and blocks env fallback without affecting a healthy agent", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-migration-isolation-"));
roots.push(root);
const legacyAgentDir = path.join(root, "agents", "legacy", "agent");
const healthyAgentDir = path.join(root, "agents", "healthy", "agent");
await fs.mkdir(legacyAgentDir, { recursive: true });
await fs.mkdir(healthyAgentDir, { recursive: true });
await fs.writeFile(
path.join(legacyAgentDir, "auth.json"),
`${JSON.stringify({ openai: { type: "api_key", key: "fake-legacy-key" } })}\n`,
);
writePersistedAuthProfileStoreRaw(
await withOpenClawTestState(
{
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "fake-healthy-key",
},
layout: "state-only",
label: "auth-migration-isolation",
env: {
OPENAI_API_KEY: "fake-env-fallback-key",
},
},
healthyAgentDir,
async (state) => {
const legacyAgentDir = state.agentDir("legacy");
const healthyAgentDir = state.agentDir("healthy");
await state.writeText(
"agents/legacy/agent/auth.json",
`${JSON.stringify({ openai: { type: "api_key", key: "fake-legacy-key" } })}\n`,
);
await state.writeAuthProfiles(
{
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "fake-healthy-key",
},
},
},
"healthy",
);
const snapshot = await prepareSecretsRuntimeSnapshot({
config: {},
env: state.env,
agentDirs: [legacyAgentDir, healthyAgentDir],
includeConfigRefs: false,
allowUnavailableSecretOwners: true,
loadablePluginOrigins: new Map(),
});
expect(snapshot.degradedOwners).toEqual([
expect.objectContaining({
ownerKind: "route",
reason: "auth profile migration required",
paths: ["auth-profile-legacy:legacy-auth"],
}),
]);
activateSecretsRuntimeSnapshot(snapshot);
await expect(
resolveApiKeyForProvider({ provider: "openai", agentDir: legacyAgentDir }),
).rejects.toMatchObject({
code: "AUTH_PROFILE_MIGRATION_REQUIRED",
action: "openclaw doctor --fix",
sourceKinds: ["legacy-auth"],
});
await expect(
resolveApiKeyForProvider({
provider: "openai",
agentDir: healthyAgentDir,
profileId: "openai:default",
lockedProfile: true,
store: snapshot.authStores.find((entry) => entry.agentDir === healthyAgentDir)?.store,
}),
).resolves.toMatchObject({ apiKey: "fake-healthy-key" });
},
);
vi.stubEnv("OPENAI_API_KEY", "fake-env-fallback-key");
const snapshot = await prepareSecretsRuntimeSnapshot({
config: {},
agentDirs: [legacyAgentDir, healthyAgentDir],
includeConfigRefs: false,
allowUnavailableSecretOwners: true,
loadablePluginOrigins: new Map(),
});
expect(snapshot.degradedOwners).toEqual([
expect.objectContaining({
ownerKind: "route",
reason: "auth profile migration required",
paths: ["auth-profile-legacy:legacy-auth"],
}),
]);
activateSecretsRuntimeSnapshot(snapshot);
await expect(
resolveApiKeyForProvider({ provider: "openai", agentDir: legacyAgentDir }),
).rejects.toMatchObject({
code: "AUTH_PROFILE_MIGRATION_REQUIRED",
action: "openclaw doctor --fix",
sourceKinds: ["legacy-auth"],
});
await expect(
resolveApiKeyForProvider({
provider: "openai",
agentDir: healthyAgentDir,
profileId: "openai:default",
lockedProfile: true,
store: snapshot.authStores.find((entry) => entry.agentDir === healthyAgentDir)?.store,
}),
).resolves.toMatchObject({ apiKey: "fake-healthy-key" });
});
});

View File

@@ -152,6 +152,7 @@ describe("full-release-validation-at-sha", () => {
const source = readFileSync("scripts/full-release-validation-at-sha.mjs", "utf8");
expect(source).toContain("actions/runs/${parentRunId}");
expect(source).toContain("workflowRun.head_sha !== workflowSha");
expect(source).toContain("return suite;");
expect(source).not.toContain('"graphql"');
expect(source).not.toContain('["run", "watch"');
});