fix(release): reuse validation evidence after tooling updates (#130850)

* fix(release): trust ancestor validation evidence

* fix(release): read candidate auth schema
This commit is contained in:
Vincent Koc
2026-08-27 17:32:58 +08:00
committed by GitHub
parent 1cbcf141ed
commit 1c80cf86cf
7 changed files with 182 additions and 32 deletions
@@ -4,6 +4,8 @@ import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { isRecord } from "../../lib/record-shared.mjs";
const AUTH_PROFILE_MACHINE_STATE_SCHEMA_VERSION = 13;
export function readSharedAuthProfileStoreText(stateDir) {
const dbPath = path.join(stateDir, "state", "openclaw.sqlite");
if (!fs.existsSync(dbPath)) {
@@ -12,19 +14,38 @@ export function readSharedAuthProfileStoreText(stateDir) {
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const schemaVersion = db.prepare("PRAGMA user_version").get()?.user_version;
if (!Number.isInteger(schemaVersion)) {
throw new Error(`invalid state schema version ${String(schemaVersion)}`);
}
// Release candidates own their persisted schema. Never let a retired row
// mask a missing canonical row once the v13 fold has occurred.
const storage =
schemaVersion >= AUTH_PROFILE_MACHINE_STATE_SCHEMA_VERSION
? {
column: "value_json",
key: "authProfiles.store",
query: "SELECT value_json FROM config_machine_state WHERE state_key = ?",
table: "config_machine_state",
}
: {
column: "store_json",
key: "shared",
query: "SELECT store_json FROM auth_profile_stores WHERE store_key = ?",
table: "auth_profile_stores",
};
const schema = db
.prepare("SELECT type FROM sqlite_schema WHERE name = ? LIMIT 1")
.get("config_machine_state");
.get(storage.table);
if (!schema) {
return "";
}
if (schema.type !== "table") {
throw new Error(`config_machine_state is ${String(schema.type)}, not a table`);
throw new Error(`${storage.table} is ${String(schema.type)}, not a table`);
}
const row = db
.prepare("SELECT value_json FROM config_machine_state WHERE state_key = ?")
.get("authProfiles.store");
return typeof row?.value_json === "string" ? row.value_json : "";
const row = db.prepare(storage.query).get(storage.key);
const value = row?.[storage.column];
return typeof value === "string" ? value : "";
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`could not read the shared auth profile store: ${detail}`, {
+12 -2
View File
@@ -1359,6 +1359,7 @@ export function validateTrustedProducerIdentity(
// Keep this predicate local: verifier source identity covers this file only.
const shaPinned = SHA_PINNED_BRANCH_PATTERN.test(manifest.workflowRef ?? "");
const protectedTagRoute = trustedIdentity.type === "tag";
let protectedTagWorkflowRefProof = "manifest-v3-protected-tag-exact-sha";
if (protectedTagRoute) {
let liveTag;
try {
@@ -1378,7 +1379,16 @@ export function validateTrustedProducerIdentity(
throw new Error("protected-tag release evidence must use a canonical release-ci branch");
}
if (manifest.workflowSha !== trustedIdentity.sha) {
throw new Error("protected-tag release evidence workflow SHA does not match trusted tooling");
const comparison = client.compareCommitLineage(manifest.workflowSha, trustedIdentity.sha);
if (
!["ahead", "identical"].includes(String(comparison.status)) ||
comparison.merge_base_commit?.sha !== manifest.workflowSha
) {
throw new Error(
"protected-tag release evidence producer is not on the trusted tooling lineage",
);
}
protectedTagWorkflowRefProof = "manifest-v3-protected-tag-tooling-lineage";
}
} else if (manifest.workflowRef !== trustedWorkflowRef && !shaPinned) {
throw new Error(
@@ -1412,7 +1422,7 @@ export function validateTrustedProducerIdentity(
throw new Error("release evidence producer workflow full ref is not trusted");
}
workflowRefProof = protectedTagRoute
? "manifest-v3-protected-tag-exact-sha"
? protectedTagWorkflowRefProof
: shaPinned
? "manifest-v3-sha-pinned-main-ancestry"
: "manifest-v3-branch";
@@ -17,28 +17,72 @@ function makeStateDir(): string {
function writeSharedDatabase(
stateDir: string,
options: { asView?: boolean; storeJson?: string } = {},
options: {
asView?: boolean;
legacyStoreJson?: string;
schemaVersion?: 12 | 13;
storeJson?: string;
} = {},
): string {
const dbPath = path.join(stateDir, "state", "openclaw.sqlite");
mkdirSync(path.dirname(dbPath), { recursive: true });
const db = new DatabaseSync(dbPath);
try {
const schemaVersion = options.schemaVersion ?? 13;
db.exec(`PRAGMA user_version = ${schemaVersion};`);
const table = schemaVersion >= 13 ? "config_machine_state" : "auth_profile_stores";
if (options.asView) {
db.exec(`
CREATE VIEW config_machine_state AS
SELECT 'authProfiles.store' AS state_key, '{}' AS value_json, 1 AS updated_at_ms;
`);
if (table === "config_machine_state") {
db.exec(`
CREATE VIEW config_machine_state AS
SELECT 'authProfiles.store' AS state_key, '{}' AS value_json, 1 AS updated_at_ms;
`);
} else {
db.exec(`
CREATE VIEW auth_profile_stores AS
SELECT 'shared' AS store_key, '{}' AS store_json, 1 AS updated_at;
`);
}
} else {
if (table === "config_machine_state") {
db.exec(`
CREATE TABLE config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL
) STRICT;
`);
db.prepare("INSERT INTO config_machine_state VALUES (?, ?, ?)").run(
"authProfiles.store",
options.storeJson ?? "{}",
Date.now(),
);
} else {
db.exec(`
CREATE TABLE auth_profile_stores (
store_key TEXT NOT NULL PRIMARY KEY,
store_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
`);
db.prepare("INSERT INTO auth_profile_stores VALUES (?, ?, ?)").run(
"shared",
options.storeJson ?? "{}",
Date.now(),
);
}
}
if (options.legacyStoreJson !== undefined) {
db.exec(`
CREATE TABLE config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL
CREATE TABLE auth_profile_stores (
store_key TEXT NOT NULL PRIMARY KEY,
store_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
`);
db.prepare("INSERT INTO config_machine_state VALUES (?, ?, ?)").run(
"authProfiles.store",
options.storeJson ?? "{}",
db.prepare("INSERT INTO auth_profile_stores VALUES (?, ?, ?)").run(
"shared",
options.legacyStoreJson,
Date.now(),
);
}
@@ -101,6 +145,26 @@ describe("auth profile store E2E assertions", () => {
expect(readSharedAuthProfileStoreText(stateDir)).toBe('{"version":1}');
});
it("reads the release-owned shared row before schema v13", () => {
const stateDir = makeStateDir();
writeSharedDatabase(stateDir, {
schemaVersion: 12,
storeJson: '{"version":1,"schema":12}',
});
expect(readSharedAuthProfileStoreText(stateDir)).toBe('{"version":1,"schema":12}');
});
it("does not accept a retired shared row for schema v13", () => {
const stateDir = makeStateDir();
writeSharedDatabase(stateDir, {
legacyStoreJson: '{"version":1,"schema":12}',
storeJson: "",
});
expect(readSharedAuthProfileStoreText(stateDir)).toBe("");
});
it("returns empty when the shared database or table is absent", () => {
const stateDir = makeStateDir();
@@ -57,6 +57,7 @@ function writeAuthProfileStoreSqlite(stateDir: string) {
const db = new DatabaseSync(databasePath);
try {
db.exec(`
PRAGMA user_version = 13;
CREATE TABLE IF NOT EXISTS config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,
@@ -43,6 +43,7 @@ function writeSharedAuthProfileStoreSqlite(home: string, store: unknown): void {
const db = new DatabaseSync(path.join(stateDir, "openclaw.sqlite"));
try {
db.exec(`
PRAGMA user_version = 13;
CREATE TABLE IF NOT EXISTS config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,
+64 -12
View File
@@ -1376,7 +1376,55 @@ describe("release CI summary child correlation", () => {
});
});
it("rejects protected-tag evidence from a same-name branch or older ancestor", () => {
it("accepts protected-tag evidence from an older trusted tooling ancestor", () => {
const trustedWorkflowSha = "7".repeat(40);
const trustedWorkflowRef = `release-publish/${trustedWorkflowSha.slice(0, 12)}-123`;
const olderWorkflowSha = "6".repeat(40);
const olderWorkflowRef = `release-ci/${olderWorkflowSha.slice(0, 12)}-1783705000000`;
const olderFixture = trustedMainPackageFixture({
manifestVersion: 3,
targetSha: "8".repeat(40),
workflowFullRef: `refs/heads/${olderWorkflowRef}`,
workflowRef: olderWorkflowRef,
workflowSha: olderWorkflowSha,
});
olderFixture.manifest.targetRef = olderFixture.targetSha;
olderFixture.client.getRef = (fullRef: string) => ({
object: { sha: trustedWorkflowSha },
ref: fullRef,
});
olderFixture.client.compareCommitLineage = (base: string, head: string) => {
expect(base).toBe(olderWorkflowSha);
expect(head).toBe(trustedWorkflowSha);
return {
merge_base_commit: { sha: olderWorkflowSha },
status: "ahead",
};
};
expect(
validateReleaseRunEvidence(
{
repository: "openclaw/openclaw",
runId: olderFixture.runId,
trustedWorkflowFullRef: `refs/tags/${trustedWorkflowRef}`,
trustedWorkflowRef,
trustedWorkflowSha,
verifierSourceContent: readFileSync(SCRIPT),
verifierSourceSha: "c".repeat(40),
},
olderFixture.client,
),
).toMatchObject({
root: {
workflowRef: olderWorkflowRef,
workflowRefProof: "manifest-v3-protected-tag-tooling-lineage",
workflowSha: olderWorkflowSha,
},
});
});
it("rejects protected-tag evidence from a same-name branch or unrelated producer", () => {
const trustedWorkflowSha = "7".repeat(40);
const trustedWorkflowRef = `release-publish/${trustedWorkflowSha.slice(0, 12)}-123`;
const validFixture = trustedMainPackageFixture({
@@ -1399,34 +1447,38 @@ describe("release CI summary child correlation", () => {
),
).toThrow("must be a protected tag");
const olderWorkflowSha = "6".repeat(40);
const olderWorkflowRef = `release-ci/${olderWorkflowSha.slice(0, 12)}-1783705000000`;
const olderFixture = trustedMainPackageFixture({
const unrelatedWorkflowSha = "6".repeat(40);
const unrelatedWorkflowRef = `release-ci/${unrelatedWorkflowSha.slice(0, 12)}-1783705000000`;
const unrelatedFixture = trustedMainPackageFixture({
manifestVersion: 3,
targetSha: "8".repeat(40),
workflowFullRef: `refs/heads/${olderWorkflowRef}`,
workflowRef: olderWorkflowRef,
workflowSha: olderWorkflowSha,
workflowFullRef: `refs/heads/${unrelatedWorkflowRef}`,
workflowRef: unrelatedWorkflowRef,
workflowSha: unrelatedWorkflowSha,
});
olderFixture.manifest.targetRef = olderFixture.targetSha;
olderFixture.client.getRef = (fullRef: string) => ({
unrelatedFixture.manifest.targetRef = unrelatedFixture.targetSha;
unrelatedFixture.client.getRef = (fullRef: string) => ({
object: { sha: trustedWorkflowSha },
ref: fullRef,
});
unrelatedFixture.client.compareCommitLineage = () => ({
merge_base_commit: { sha: "5".repeat(40) },
status: "diverged",
});
expect(() =>
validateReleaseRunEvidence(
{
repository: "openclaw/openclaw",
runId: olderFixture.runId,
runId: unrelatedFixture.runId,
trustedWorkflowFullRef: `refs/tags/${trustedWorkflowRef}`,
trustedWorkflowRef,
trustedWorkflowSha,
verifierSourceContent: readFileSync(SCRIPT),
verifierSourceSha: "c".repeat(40),
},
olderFixture.client,
unrelatedFixture.client,
),
).toThrow("does not match trusted tooling");
).toThrow("not on the trusted tooling lineage");
const sameNameFixture = trustedMainPackageFixture({
manifestVersion: 3,
@@ -38,6 +38,7 @@ function writeAuthProfileStoreSqlite(stateDir: string, store: unknown) {
const db = new DatabaseSync(databasePath);
try {
db.exec(`
PRAGMA user_version = 13;
CREATE TABLE IF NOT EXISTS config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,