fix(release): bind frozen harness layouts to candidate

This commit is contained in:
Vincent Koc
2026-08-21 09:52:04 -07:00
parent af6c5d3e57
commit afc3535d25
6 changed files with 256 additions and 47 deletions
+2 -1
View File
@@ -476,8 +476,9 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
fetch-depth: 1
fetch-depth: 0
fetch-tags: false
filter: blob:none
persist-credentials: false
submodules: false
@@ -77,17 +77,14 @@ function extractStatusSection(text, title) {
return stripAnsi(section.join("\n"));
}
function readSharedAuthProfileStoreText(stateDir) {
const dbPath = path.join(stateDir, "state", "openclaw.sqlite");
function readAuthProfileStoreText(dbPath, query, storeKey) {
if (!fs.existsSync(dbPath)) {
return "";
}
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const row = db
.prepare("SELECT store_json FROM auth_profile_stores WHERE store_key = ?")
.get("shared");
const row = db.prepare(query).get(storeKey);
return typeof row?.store_json === "string" ? row.store_json : "";
} catch {
return "";
@@ -96,10 +93,50 @@ function readSharedAuthProfileStoreText(stateDir) {
}
}
function readCurrentAuthProfileStoreText(stateDir) {
const dbPath = path.join(stateDir, "state", "openclaw.sqlite");
return readAuthProfileStoreText(
dbPath,
"SELECT store_json FROM auth_profile_stores WHERE store_key = ?",
"shared",
);
}
function readLegacyAuthProfileStoreText(stateDir) {
const dbPath = path.join(stateDir, "agents", "main", "agent", "openclaw-agent.sqlite");
return readAuthProfileStoreText(
dbPath,
"SELECT store_json FROM auth_profile_store WHERE store_key = ?",
"primary",
);
}
function assertOnboardState() {
const home = process.argv[3];
const expectedLayout = process.argv[3];
const home = process.argv[4];
if (expectedLayout !== "legacy" && expectedLayout !== "current") {
throw new Error(`unsupported onboard auth profile store layout: ${expectedLayout}`);
}
const stateDir = path.join(home, ".openclaw");
const configPath = path.join(stateDir, "openclaw.json");
if (!fs.existsSync(configPath)) {
throw new Error("onboard did not write openclaw.json");
}
const legacyAuthStoreText = readLegacyAuthProfileStoreText(stateDir);
const currentAuthStoreText = readCurrentAuthProfileStoreText(stateDir);
if (legacyAuthStoreText && currentAuthStoreText) {
throw new Error("onboard persisted mixed legacy and current auth profile stores");
}
const observedLayout = legacyAuthStoreText ? "legacy" : currentAuthStoreText ? "current" : "";
if (!observedLayout) {
throw new Error("onboard did not persist auth profile store");
}
if (observedLayout !== expectedLayout) {
throw new Error(
`onboard persisted ${observedLayout} auth profile store; expected ${expectedLayout}`,
);
}
const legacyAuthDatabase = path.join(
stateDir,
"agents",
@@ -107,14 +144,10 @@ function assertOnboardState() {
"agent",
"openclaw-agent.sqlite",
);
if (!fs.existsSync(configPath)) {
throw new Error("onboard did not write openclaw.json");
}
if (fs.existsSync(legacyAuthDatabase)) {
if (expectedLayout === "current" && fs.existsSync(legacyAuthDatabase)) {
throw new Error("onboard created the retired main-agent auth database");
}
const authStoreText = readSharedAuthProfileStoreText(stateDir);
const authStoreText = expectedLayout === "legacy" ? legacyAuthStoreText : currentAuthStoreText;
if (!authStoreText) {
throw new Error("onboard did not persist auth profile store");
}
@@ -14,6 +14,7 @@ HOST_BUILD="${OPENCLAW_NPM_ONBOARD_HOST_BUILD:-1}"
PACKAGE_TGZ="${OPENCLAW_CURRENT_PACKAGE_TGZ:-}"
CHANNEL="${OPENCLAW_NPM_ONBOARD_CHANNEL:-telegram}"
USE_SOURCE_PLUGIN_PACKAGE="${OPENCLAW_NPM_ONBOARD_USE_SOURCE_PLUGIN_PACKAGE:-0}"
AUTH_PROFILE_STORE_CUTOVER_SHA="a8a9f284fb91af6a9d78fe66f9141eb01e009b21"
JSON_ARTIFACT_MAX_BYTES="$(
docker_e2e_read_positive_int_env OPENCLAW_NPM_ONBOARD_JSON_ARTIFACT_MAX_BYTES 1048576
)"
@@ -24,6 +25,50 @@ run_log=""
plugin_pack_dir=""
plugin_package_args=()
resolve_auth_profile_store_layout() {
local selected_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}"
local source_sha
source_sha="$(git -C "$SOURCE_ROOT" rev-parse HEAD)"
if [ -z "$selected_sha" ]; then
selected_sha="$source_sha"
fi
if [[ ! "$selected_sha" =~ ^[0-9a-f]{40}$ ]] || [ "$source_sha" != "$selected_sha" ]; then
echo "Cannot bind npm onboarding auth layout to candidate $selected_sha at $source_sha." >&2
return 1
fi
if [ "$(git -C "$SOURCE_ROOT" rev-parse --is-shallow-repository)" = "true" ]; then
git -C "$SOURCE_ROOT" fetch --no-tags --filter=blob:none --deepen=1024 origin \
"$selected_sha" "$AUTH_PROFILE_STORE_CUTOVER_SHA"
elif ! git -C "$SOURCE_ROOT" cat-file -e "$AUTH_PROFILE_STORE_CUTOVER_SHA^{commit}"; then
git -C "$SOURCE_ROOT" fetch --no-tags --filter=blob:none --depth=1024 origin \
"$AUTH_PROFILE_STORE_CUTOVER_SHA"
fi
if ! git -C "$SOURCE_ROOT" merge-base "$AUTH_PROFILE_STORE_CUTOVER_SHA" "$selected_sha" \
>/dev/null; then
echo "Cannot prove candidate $selected_sha auth layout ancestry relative to $AUTH_PROFILE_STORE_CUTOVER_SHA." >&2
return 1
fi
set +e
git -C "$SOURCE_ROOT" merge-base --is-ancestor \
"$AUTH_PROFILE_STORE_CUTOVER_SHA" "$selected_sha"
local ancestry_status=$?
set -e
if [ "$ancestry_status" -eq 0 ]; then
printf '%s\n' "current"
elif [ "$ancestry_status" -eq 1 ]; then
# Frozen release branches can diverge before the cutover; absence of the
# cutover commit is the legacy contract, not a requirement to follow main.
printf '%s\n' "legacy"
else
echo "Cannot determine candidate $selected_sha auth layout ancestry relative to $AUTH_PROFILE_STORE_CUTOVER_SHA." >&2
return 1
fi
}
AUTH_PROFILE_STORE_LAYOUT="$(resolve_auth_profile_store_layout)"
cleanup() {
if [ -n "${PACKAGE_TGZ:-}" ]; then
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
@@ -103,6 +148,7 @@ echo "Running npm tarball onboard/channel/agent Docker E2E ($CHANNEL)..."
if ! docker_e2e_run_with_harness \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e OPENCLAW_NPM_ONBOARD_CHANNEL="$CHANNEL" \
-e "OPENCLAW_NPM_ONBOARD_AUTH_LAYOUT=$AUTH_PROFILE_STORE_LAYOUT" \
-e "OPENCLAW_NPM_ONBOARD_JSON_ARTIFACT_MAX_BYTES=$JSON_ARTIFACT_MAX_BYTES" \
-e "OPENCLAW_NPM_ONBOARD_STATUS_TEXT_MAX_BYTES=$STATUS_TEXT_MAX_BYTES" \
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
@@ -208,7 +254,8 @@ openclaw onboard --non-interactive --accept-risk \
--skip-health \
--json >/tmp/openclaw-onboard.json
node scripts/e2e/lib/npm-onboard-channel-agent/assertions.mjs assert-onboard-state "$HOME"
node scripts/e2e/lib/npm-onboard-channel-agent/assertions.mjs \
assert-onboard-state "$OPENCLAW_NPM_ONBOARD_AUTH_LAYOUT" "$HOME"
openclaw_e2e_assert_dep_absent "$DEP_SENTINEL" "$HOME/.openclaw"
+15
View File
@@ -2716,6 +2716,21 @@ docker_e2e_docker_run_cmd run demo
expect(script).not.toContain("/tmp/openclaw-channel-add.log");
});
it("binds npm onboarding auth layout to the exact candidate ancestry", () => {
const script = readFileSync(NPM_ONBOARD_CHANNEL_AGENT_DOCKER_E2E_PATH, "utf8");
expectTextToIncludeAll(script, [
'AUTH_PROFILE_STORE_CUTOVER_SHA="a8a9f284fb91af6a9d78fe66f9141eb01e009b21"',
'selected_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}"',
"fetch --no-tags --filter=blob:none --deepen=1024 origin",
'merge-base "$AUTH_PROFILE_STORE_CUTOVER_SHA" "$selected_sha"',
'merge-base --is-ancestor \\\n "$AUTH_PROFILE_STORE_CUTOVER_SHA" "$selected_sha"',
"OPENCLAW_NPM_ONBOARD_AUTH_LAYOUT=$AUTH_PROFILE_STORE_LAYOUT",
]);
expect(script).toContain('elif [ "$ancestry_status" -eq 1 ]; then');
expect(script).toContain('if ! git -C "$SOURCE_ROOT" merge-base');
});
it("keeps real-TTY onboarding drivers aligned with the first-agent prompt", () => {
expectOrderedScriptFragments(readFileSync(RELEASE_TYPED_ONBOARDING_SCENARIO_PATH, "utf8"), [
'wait_for_log "Continue?"',
@@ -37,7 +37,7 @@ function writeOnboardConfig(home: string): void {
);
}
function writeSharedAuthProfileStoreSqlite(home: string, store: unknown): void {
function writeCurrentAuthProfileStoreSqlite(home: string, store: unknown): void {
const stateDir = path.join(home, ".openclaw", "state");
fs.mkdirSync(stateDir, { recursive: true });
const db = new DatabaseSync(path.join(stateDir, "openclaw.sqlite"));
@@ -60,6 +60,29 @@ function writeSharedAuthProfileStoreSqlite(home: string, store: unknown): void {
}
}
function writeLegacyAuthProfileStoreSqlite(home: string, store: unknown): void {
const agentDir = path.join(home, ".openclaw", "agents", "main", "agent");
fs.mkdirSync(agentDir, { recursive: true });
const db = new DatabaseSync(path.join(agentDir, "openclaw-agent.sqlite"));
try {
db.exec(`
CREATE TABLE IF NOT EXISTS auth_profile_store (
store_key TEXT NOT NULL PRIMARY KEY,
store_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
`);
db.prepare(
`
INSERT INTO auth_profile_store (store_key, store_json, updated_at)
VALUES (?, ?, ?)
`,
).run("primary", JSON.stringify(store), Date.now());
} finally {
db.close();
}
}
function runAssert(home: string, channel: string, ...tokens: string[]) {
return spawnSync(
process.execPath,
@@ -75,8 +98,8 @@ function runAssert(home: string, channel: string, ...tokens: string[]) {
);
}
function runOnboardAssert(home: string) {
return spawnSync(process.execPath, [assertionsPath, "assert-onboard-state", home], {
function runOnboardAssert(home: string, layout: "current" | "legacy") {
return spawnSync(process.execPath, [assertionsPath, "assert-onboard-state", layout, home], {
encoding: "utf8",
env: {
...process.env,
@@ -218,33 +241,36 @@ describe("npm onboard channel agent assertions", () => {
}
});
it("validates OpenAI env refs from the shared SQLite auth profile store", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
const agentDir = path.join(tempDir, ".openclaw", "agents", "main", "agent");
it.each([
["legacy", writeLegacyAuthProfileStoreSqlite],
["current", writeCurrentAuthProfileStoreSqlite],
] as const)(
"validates OpenAI env refs from the %s SQLite auth profile store",
(layout, writeStore) => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
try {
writeOnboardConfig(tempDir);
writeSharedAuthProfileStoreSqlite(tempDir, {
version: 1,
profiles: {
"openai:api-key": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
try {
writeOnboardConfig(tempDir);
writeStore(tempDir, {
version: 1,
profiles: {
"openai:api-key": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
},
});
});
const result = runOnboardAssert(tempDir);
const result = runOnboardAssert(tempDir, layout);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(fs.existsSync(agentDir)).toBe(false);
expect(fs.existsSync(path.join(agentDir, "auth-profiles.json"))).toBe(false);
} finally {
fs.rmSync(tempDir, { force: true, recursive: true });
}
});
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
} finally {
fs.rmSync(tempDir, { force: true, recursive: true });
}
},
);
it("rejects auth profile stores without a usable OpenAI env ref", () => {
const cases: unknown[] = [
@@ -262,9 +288,9 @@ describe("npm onboard channel agent assertions", () => {
try {
writeOnboardConfig(tempDir);
writeSharedAuthProfileStoreSqlite(tempDir, store);
writeCurrentAuthProfileStoreSqlite(tempDir, store);
const result = runOnboardAssert(tempDir);
const result = runOnboardAssert(tempDir, "current");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("auth profile did not persist OPENAI_API_KEY env ref");
@@ -278,7 +304,7 @@ describe("npm onboard channel agent assertions", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
try {
writeOnboardConfig(tempDir);
writeSharedAuthProfileStoreSqlite(tempDir, {
writeCurrentAuthProfileStoreSqlite(tempDir, {
version: 1,
profiles: {
"openai:api-key": {
@@ -289,7 +315,7 @@ describe("npm onboard channel agent assertions", () => {
},
});
const result = runOnboardAssert(tempDir);
const result = runOnboardAssert(tempDir, "current");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("auth profile persisted the raw OpenAI test key");
@@ -298,13 +324,75 @@ describe("npm onboard channel agent assertions", () => {
}
});
it("rejects a fresh install that recreates the retired main-agent auth database", () => {
it("rejects mixed legacy and current auth profile stores", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
try {
writeOnboardConfig(tempDir);
const store = {
version: 1,
profiles: {
"openai:api-key": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
};
writeLegacyAuthProfileStoreSqlite(tempDir, store);
writeCurrentAuthProfileStoreSqlite(tempDir, store);
const result = runOnboardAssert(tempDir, "current");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"onboard persisted mixed legacy and current auth profile stores",
);
} finally {
fs.rmSync(tempDir, { force: true, recursive: true });
}
});
it.each([
["current", "legacy", writeCurrentAuthProfileStoreSqlite],
["legacy", "current", writeLegacyAuthProfileStoreSqlite],
] as const)(
"rejects the %s layout when the candidate requires %s",
(observedLayout, expectedLayout, writeStore) => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
try {
writeOnboardConfig(tempDir);
writeStore(tempDir, {
version: 1,
profiles: {
"openai:api-key": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
});
const result = runOnboardAssert(tempDir, expectedLayout);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
`onboard persisted ${observedLayout} auth profile store; expected ${expectedLayout}`,
);
} finally {
fs.rmSync(tempDir, { force: true, recursive: true });
}
},
);
it("keeps the current-layout ban on the retired main-agent database", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
const legacyAgentDir = path.join(tempDir, ".openclaw", "agents", "main", "agent");
try {
writeOnboardConfig(tempDir);
writeSharedAuthProfileStoreSqlite(tempDir, {
writeCurrentAuthProfileStoreSqlite(tempDir, {
version: 1,
profiles: {
"openai:api-key": {
@@ -317,7 +405,7 @@ describe("npm onboard channel agent assertions", () => {
fs.mkdirSync(legacyAgentDir, { recursive: true });
new DatabaseSync(path.join(legacyAgentDir, "openclaw-agent.sqlite")).close();
const result = runOnboardAssert(tempDir);
const result = runOnboardAssert(tempDir, "current");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("onboard created the retired main-agent auth database");
@@ -326,6 +414,21 @@ describe("npm onboard channel agent assertions", () => {
}
});
it("rejects a missing auth profile store", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-onboard-assertions-"));
try {
writeOnboardConfig(tempDir);
const result = runOnboardAssert(tempDir, "legacy");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("onboard did not persist auth profile store");
} finally {
fs.rmSync(tempDir, { force: true, recursive: true });
}
});
it("validates channel tokens in their canonical config fields", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-channel-assertions-"));
try {
@@ -754,6 +754,16 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
expect(extensionShard.strategy.matrix).toBe(
"${{ fromJson(needs.preflight.outputs.plugin_prerelease_extension_matrix) }}",
);
expect(
extensionShard.steps.find((step: WorkflowStep) => step.name === "Checkout").with,
).toEqual({
"fetch-depth": 0,
"fetch-tags": false,
filter: "blob:none",
"persist-credentials": false,
ref: "${{ needs.preflight.outputs.checkout_revision }}",
submodules: false,
});
expect(
extensionShard.steps.find((step: WorkflowStep) => step.name === "Run extension shard").run,
).toContain("--retry=1");