mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 05:51:39 +00:00
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:
committed by
GitHub
parent
d4ed57bc88
commit
3c67fcc45d
@@ -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"));
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> = {
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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"');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user