mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
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:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user