fix(ci): restore deterministic main checks (#120501)

* fix(plugin-sdk): stabilize session catalog baseline

* test(doctor): isolate bind persistence inventory

* fix(ci): prepare max-lines base during checkout

* test(sessions): align freshness regression fixtures

* style(tests): format context usage fixture

* fix(ci): preserve token provenance and isolate fork credentials

Finish the canonical token-provenance fixtures while preserving the
latest maintainer-owned unavailable-usage ordering. Limit the diff-base
GitHub token to manual non-release comparisons and guard that fork
isolation invariant in the existing workflow regression test.

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>

* test(ui): identify actual managed image eviction

* fix(ci): reuse the validated protocol comparison base

Consume the immutable diff base already resolved and validated by
preflight for every CI event. Remove the dead duplicate manual
GitHub lookup and unreachable fallbacks, and guard the single
authoritative path without changing fork credential isolation.

* test(sessions): preserve stale total expectation

* test(ui): arm reconnect deferral before disconnect

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
This commit is contained in:
Peter Steinberger
2026-08-08 03:36:35 -07:00
committed by GitHub
parent 6ae7a8684a
commit 95ea82b1e5
10 changed files with 181 additions and 229 deletions
+38 -88
View File
@@ -252,7 +252,9 @@ jobs:
- name: Resolve exact diff base
id: diff_base
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
EVENT_BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha || '' }}
GH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && !inputs.release_gate && github.token || '' }}
PULL_REQUEST_NUMBER: ${{ inputs.pull_request_number }}
RELEASE_GATE: ${{ inputs.release_gate }}
run: |
@@ -265,6 +267,18 @@ jobs:
base_sha="$(node scripts/lib/merge-head-diff-base.mjs \
--base "$base_sha" --head HEAD --prefer-first-parent)"
fi
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "$RELEASE_GATE" != "true" ]; then
default_sha="$(git ls-remote origin "refs/heads/${DEFAULT_BRANCH}" | awk 'NR == 1 { print $1}')"
if [[ ! "$default_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve the default branch head for the manual target." >&2
exit 1
fi
base_sha="$(
gh api --method GET \
"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${head_sha}" \
--jq '.merge_base_commit.sha'
)"
fi
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "$RELEASE_GATE" = "true" ]; then
merge_ref="refs/remotes/origin/release-gate-merge"
timeout --signal=TERM --kill-after=10s 120s git fetch \
@@ -279,6 +293,10 @@ jobs:
base_sha="$(git rev-parse "${merge_ref}^1")"
head_sha="$(git rev-parse "$merge_ref")"
fi
if [[ ! "$base_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve an exact diff base for ${GITHUB_EVENT_NAME}." >&2
exit 1
fi
echo "sha=$base_sha" >> "$GITHUB_OUTPUT"
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
@@ -1030,10 +1048,15 @@ jobs:
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
fetch_refs=("+${CHECKOUT_SHA}:refs/remotes/origin/ci-target")
if [ -n "${CHECKOUT_BASE_SHA:-}" ]; then
fetch_refs+=("+${CHECKOUT_BASE_SHA}:refs/remotes/origin/ci-max-lines-base")
fi
timeout --signal=TERM --kill-after=10s 120s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
"${fetch_refs[@]}" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
@@ -1533,6 +1556,8 @@ jobs:
fail-fast: false
max-parallel: 12
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
env:
CHECKOUT_BASE_SHA: ${{ matrix.task == 'max-lines-ratchet' && needs.preflight.outputs.diff_base_revision || '' }}
steps:
- *linux_node_checkout_step
- name: Prepare release-gate max-lines merge tree
@@ -1577,7 +1602,7 @@ jobs:
exit 1
fi
git checkout --detach "$merge_sha"
echo "RATCHET_RELEASE_BASE_SHA=${frozen_base_sha}" >> "$GITHUB_ENV"
echo "RATCHET_BASE_REF=${frozen_base_sha}" >> "$GITHUB_ENV"
echo "RATCHET_RELEASE_MERGE_TREE=true" >> "$GITHUB_ENV"
- name: Setup Node environment
@@ -1590,41 +1615,10 @@ jobs:
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Resolve manual protocol base
id: protocol_manual_base
if: matrix.task == 'bundled-protocol' && github.event_name == 'workflow_dispatch' && !inputs.release_gate
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ github.token }}
TARGET_SHA: ${{ needs.preflight.outputs.checkout_revision }}
shell: bash
run: |
set -euo pipefail
default_sha="$(git ls-remote origin "refs/heads/${DEFAULT_BRANCH}" | awk 'NR == 1 { print $1 }')"
if [[ ! "$default_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve the default branch head for the manual protocol target." >&2
exit 1
fi
merge_base_sha="$(
gh api --method GET \
"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${TARGET_SHA}" \
--jq '.merge_base_commit.sha'
)"
if [[ ! "$merge_base_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve the manual protocol target merge base." >&2
exit 1
fi
echo "sha=${merge_base_sha}" >> "$GITHUB_OUTPUT"
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
env:
GH_TOKEN: ${{ matrix.task == 'max-lines-ratchet' && github.token || '' }}
OPENCLAW_TEST_PROJECTS_PARALLEL: 3
PROTOCOL_MANUAL_BASE_SHA: ${{ steps.protocol_manual_base.outputs.sha }}
PROTOCOL_SINCE_BASE_SHA: ${{ needs.preflight.outputs.diff_base_revision }}
RATCHET_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RATCHET_EVENT_BASE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }}
RATCHET_MANUAL_TARGET_SHA: ${{ github.event_name == 'workflow_dispatch' && !inputs.release_gate && needs.preflight.outputs.checkout_revision || '' }}
RATCHET_PR_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
TASK: ${{ matrix.task }}
shell: bash
@@ -1636,41 +1630,11 @@ jobs:
process.exit(Object.hasOwn(scripts, process.argv[1]) ? 0 : 1);
' "$1"
}
resolve_manual_merge_base() {
local default_branch="${RATCHET_DEFAULT_BRANCH:-main}"
local default_sha
local merge_base_sha
default_sha="$(git ls-remote origin "refs/heads/${default_branch}" | awk 'NR == 1 { print $1 }')"
if [[ ! "$default_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve the default branch head for the manual target." >&2
return 1
fi
merge_base_sha="$(
gh api --method GET \
"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${RATCHET_MANUAL_TARGET_SHA}" \
--jq '.merge_base_commit.sha'
)"
if [[ ! "$merge_base_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not resolve the manual target merge base." >&2
return 1
fi
printf '%s\n' "$merge_base_sha"
}
case "$TASK" in
bundled-protocol)
pnpm test:bundled
if [[ -n "${PROTOCOL_SINCE_BASE_SHA:-}" ]]; then
git fetch --no-tags --no-recurse-submodules --depth=1 origin \
"+${PROTOCOL_SINCE_BASE_SHA}:refs/remotes/origin/protocol-since-base"
elif [[ -n "${PROTOCOL_MANUAL_BASE_SHA:-}" ]]; then
PROTOCOL_SINCE_BASE_SHA="$PROTOCOL_MANUAL_BASE_SHA"
export PROTOCOL_SINCE_BASE_SHA
git fetch --no-tags --no-recurse-submodules --depth=1 origin \
"+${PROTOCOL_SINCE_BASE_SHA}:refs/remotes/origin/protocol-since-base"
else
git fetch --no-tags --no-recurse-submodules --depth=1 origin \
"+refs/heads/main:refs/remotes/origin/main"
fi
git fetch --no-tags --no-recurse-submodules --depth=1 origin \
"+${PROTOCOL_SINCE_BASE_SHA}:refs/remotes/origin/protocol-since-base"
pnpm protocol:check
;;
contracts-plugins-ci-routing)
@@ -1685,37 +1649,23 @@ jobs:
echo "Current CI targets must provide check:max-lines-ratchet." >&2
exit 1
fi
base_sha="${RATCHET_EVENT_BASE_SHA:-${RATCHET_RELEASE_BASE_SHA:-}}"
if [[ "$base_sha" == "0000000000000000000000000000000000000000" ]]; then
base_sha=""
base_ref="${RATCHET_BASE_REF:-refs/remotes/origin/ci-max-lines-base}"
if ! git cat-file -e "${base_ref}^{commit}" 2>/dev/null; then
echo "Prepared max-lines base ${base_ref} is unavailable." >&2
exit 1
fi
base_ref=""
if [[ -n "${RATCHET_PR_HEAD_SHA:-}" ]]; then
mapfile -t merge_parents < <(git cat-file -p HEAD | sed -n 's/^parent //p')
if [[ "${#merge_parents[@]}" != "2" || "${merge_parents[1]:-}" != "$RATCHET_PR_HEAD_SHA" ]]; then
echo "Pull request checkout is not the expected two-parent merge tree." >&2
exit 1
fi
merge_base="${merge_parents[0]}"
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags --depth=1 origin \
"+${merge_base}:refs/remotes/origin/ci-max-lines-base"
base_ref="refs/remotes/origin/ci-max-lines-base"
elif [[ -n "$base_sha" ]]; then
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags --depth=1 origin \
"+${base_sha}:refs/remotes/origin/ci-max-lines-base"
base_ref="refs/remotes/origin/ci-max-lines-base"
elif [[ -n "${RATCHET_MANUAL_TARGET_SHA:-}" ]]; then
merge_base_sha="$(resolve_manual_merge_base)"
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags --depth=1 origin \
"+${merge_base_sha}:refs/remotes/origin/ci-max-lines-base"
base_ref="refs/remotes/origin/ci-max-lines-base"
else
default_branch="${RATCHET_DEFAULT_BRANCH:-main}"
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags --depth=1 origin \
"+refs/heads/${default_branch}:refs/remotes/origin/ci-max-lines-base"
base_ref="refs/remotes/origin/ci-max-lines-base"
prepared_base="$(git rev-parse "$base_ref")"
if [[ "${merge_parents[0]}" != "$prepared_base" ]]; then
echo "Pull request merge base does not match the prepared preflight base." >&2
exit 1
fi
fi
unset GH_TOKEN
pnpm check:max-lines-ratchet --base "$base_ref"
if [[ "${RATCHET_RELEASE_MERGE_TREE:-}" == "true" ]]; then
node scripts/run-oxlint.mjs src ui/src packages extensions
@@ -122,7 +122,7 @@ aa8a411ad37c1d1143b67376bf2d20255b9eedff61d80815f42e4f8ed7bd8e58 module/secret-
57dcb1462d4c4f9a98d934c4ca975b163d704758af9821a64001ff3ac05637c3 module/secret-input-runtime
dc0ee07d392a85c218939000b28c0138f139215da00f5592b34a68ba8e29a25d module/secret-ref-runtime
f97549081955e412d8eb64070c64bb5921bb1324744d824db05767d3adcce403 module/security-runtime
8f57eaf07620598f0a62cf603d450d9a9da2d4ea7a867f13aadc1212019e5eb9 module/session-catalog
b18d65a8abbed79bcb77e34679b7aeb03f2e9101271f48298b47eb839c33a379 module/session-catalog
50f5e344f98c27570b7a30e32a906b612e2383d21f102e88cd93e1d5425a6de9 module/session-discussion
f112bdabc51ba8659b37d0a6f6a32a2b1d471e5b49b56e108bf750ec55a7ea71 module/session-store-runtime
36affbe151431a6141664b6838e20f2d121ff210d57a3c1b4b41a8818b5c81d8 module/setup
@@ -158,60 +158,6 @@ describe("AgentSession loop correctness", () => {
);
});
it("does not pre-prompt compact from usage before a zero unavailable marker", async () => {
const model = { ...testModel, contextWindow: 1_000 };
const sessionManager = SessionManager.inMemory();
appendHistory(
sessionManager,
createAssistant(model, [{ type: "text", text: "old cumulative turn" }], "stop", 950),
);
sessionManager.appendMessage({ role: "user", content: "CLI prompt", timestamp: Date.now() });
sessionManager.appendMessage({
...createAssistant(model, [{ type: "text", text: "usage unavailable" }]),
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
contextUsage: { state: "unavailable" },
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
});
const settingsManager = SettingsManager.inMemory({
compaction: { enabled: true, reserveTokens: 100, keepRecentTokens: 20 },
retry: { enabled: false },
});
const compactionEvents: AgentSessionEvent[] = [];
streamMocks.streamSimple.mockImplementation((activeModel: Model) =>
createAssistantResultStream(
createAssistant(activeModel, [{ type: "text", text: "complete answer" }], "stop", 20),
),
);
const { session } = await createTestSession({
model,
sessionManager,
settingsManager,
resourceLoader: createResourceLoader(createCompactionHandlers()),
});
session.subscribe((event) => {
if (event.type === "compaction_start" || event.type === "compaction_end") {
compactionEvents.push(event);
}
});
expect(session.messages.at(-1)).toMatchObject({
role: "assistant",
usage: { contextUsage: { state: "unavailable" } },
});
expect(session.getContextUsage()?.tokens).toBeLessThan(900);
await session.prompt("continue after CLI turn");
expect(streamMocks.streamSimple).toHaveBeenCalledOnce();
expect(compactionEvents).toEqual([]);
expect(session.getLastAssistantText()).toBe("complete answer");
});
it("skips threshold maintenance when embedded auto-compaction is disabled", async () => {
const settingsManager = SettingsManager.inMemory({
compaction: { enabled: false, reserveTokens: 0, keepRecentTokens: 1 },
@@ -1,8 +1,45 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import type { AgentMessage } from "../runtime/index.js";
import { AgentSession } from "./agent-session.js";
describe("AgentSession context usage", () => {
it("does not compact an unavailable marker when current context is below threshold", async () => {
const runAutoCompaction = vi.fn();
const checkCompaction = (
AgentSession.prototype as unknown as {
checkCompaction: (message: AgentMessage, skipAbortedCheck: boolean) => Promise<boolean>;
}
).checkCompaction;
const compacted = await checkCompaction.call(
{
settingsManager: {
getCompactionSettings: () => ({
enabled: true,
reserveTokens: 100,
keepRecentTokens: 20,
}),
},
model: { provider: "test", id: "model", contextWindow: 1_000 },
sessionManager: { getBranch: () => [] },
getContextUsage: () => ({ tokens: 100 }),
runAutoCompaction,
},
{
role: "assistant",
provider: "test",
model: "model",
stopReason: "stop",
timestamp: Date.now(),
usage: { contextUsage: { state: "unavailable" } },
} as AgentMessage,
false,
);
expect(compacted).toBe(false);
expect(runAutoCompaction).not.toHaveBeenCalled();
});
it("preserves an earlier exact snapshot when unavailable usage precedes any compaction", () => {
const messages = [
{
@@ -1,50 +1,68 @@
// Verifies Doctor persists legacy gateway bind repairs through the real config writer.
import fs from "node:fs/promises";
import { describe, expect, it, vi } from "vitest";
import { withTempHome, writeOpenClawConfig } from "../config/test-helpers.js";
import { afterEach, describe, expect, it, vi } from "vitest";
import { readConfigFileSnapshot } from "../config/config.js";
import { withEnvOverride, withTempHome, writeOpenClawConfig } from "../config/test-helpers.js";
import { runInitialConfigWriteHealth } from "../flows/doctor-health-contribution-runners.config.js";
import type { DoctorHealthFlowContext } from "../flows/doctor-health-contribution-types.js";
import type { RuntimeEnv } from "../runtime.js";
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
import { createDoctorPrompter, type DoctorOptions } from "./doctor-prompter.js";
import { migrateLegacyConfig } from "./doctor/shared/legacy-config-migrate.js";
describe("Doctor gateway bind persistence", () => {
afterEach(() => {
closeOpenClawStateDatabaseForTest();
});
it.each([
["localhost", "loopback"],
["0.0.0.0", "lan"],
] as const)("persists gateway bind %s as %s", async (legacyBind, canonicalBind) => {
await withTempHome(async (home) => {
const configPath = await writeOpenClawConfig(home, {
gateway: { mode: "local", bind: legacyBind },
await withEnvOverride({ OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" }, async () => {
// This core writer regression needs the authoritative empty bundled-plugin inventory.
const configPath = await writeOpenClawConfig(home, {
gateway: { mode: "local", bind: legacyBind },
});
const runtime: RuntimeEnv = {
error: vi.fn(),
exit: vi.fn(),
log: vi.fn(),
};
const options: DoctorOptions = { nonInteractive: true, repair: true };
const prompter = createDoctorPrompter({ runtime, options });
const configResult = await loadAndMaybeMigrateDoctorConfig({
options,
confirm: (params) => prompter.confirm(params),
runtime,
prompter,
});
const ctx: DoctorHealthFlowContext = {
runtime,
options,
prompter,
configResult,
cfg: configResult.cfg,
cfgForPersistence: structuredClone(configResult.cfg),
sourceConfigValid: configResult.sourceConfigValid ?? true,
configPath,
stateDirExistedAtStart: true,
...(configResult.runWithPluginMetadataSnapshot
? { runWithPluginMetadataSnapshot: configResult.runWithPluginMetadataSnapshot }
: {}),
...(configResult.invalidatePluginMetadataSnapshot
? { invalidatePluginMetadataSnapshot: configResult.invalidatePluginMetadataSnapshot }
: {}),
};
await runInitialConfigWriteHealth(ctx);
const snapshot = await readConfigFileSnapshot();
expect(snapshot.valid).toBe(true);
expect(snapshot.config.gateway?.bind).toBe(canonicalBind);
expect(await fs.readFile(configPath, "utf-8")).not.toContain(`"bind": "${legacyBind}"`);
});
const runtime: RuntimeEnv = {
error: vi.fn(),
exit: vi.fn(),
log: vi.fn(),
};
const options: DoctorOptions = { nonInteractive: true, repair: true };
const prompter = createDoctorPrompter({ runtime, options });
const migration = migrateLegacyConfig({ gateway: { mode: "local", bind: legacyBind } });
expect(migration.config).not.toBeNull();
const cfg = migration.config!;
const configResult = { cfg, shouldWriteConfig: true };
const ctx: DoctorHealthFlowContext = {
runtime,
options,
prompter,
configResult,
cfg,
cfgForPersistence: structuredClone(cfg),
sourceConfigValid: true,
configPath,
stateDirExistedAtStart: true,
};
await runInitialConfigWriteHealth(ctx);
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8"));
expect(persisted.gateway?.bind).toBe(canonicalBind);
expect(persisted.gateway?.bind).not.toBe(legacyBind);
});
});
});
+5
View File
@@ -28,6 +28,7 @@ const TEST_ENTRYPOINTS = [
"provider-selection-runtime",
"provider-web-search-config-contract",
"realtime-voice",
"session-catalog",
"sqlite-runtime-testing",
] as const;
@@ -194,6 +195,10 @@ describe("Plugin SDK API baseline", () => {
expect(findDeclaration("ProviderSelection")).toContain(
"export type ProviderSelection<TProvider> =",
);
expect(findDeclaration("SessionCatalogEntrySummary")).toContain(
"export interface SessionCatalogEntrySummary",
);
expect(findDeclaration("SessionCatalogEntrySummary")).toContain("entry: SessionEntry;");
expect(rendered.json).not.toContain('"line":');
expect(rendered.jsonl).not.toContain('"sourceLine":');
});
+5 -3
View File
@@ -7,6 +7,7 @@ import type {
SessionsCatalogReadResult,
} from "../../packages/gateway-protocol/src/schema/sessions-catalog.js";
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginRuntime } from "./runtime/types.js";
@@ -57,9 +58,10 @@ export type SessionCatalogCreateTarget = {
agentRuntime: string;
};
export type SessionCatalogEntrySummary = ReturnType<
PluginRuntime["agent"]["session"]["listSessionEntries"]
>[number];
export interface SessionCatalogEntrySummary {
sessionKey: string;
entry: SessionEntry;
}
/** Shared, logically frozen store state for one request; copy locally before mutating. */
export type SessionCatalogEntrySnapshot = {
+39 -46
View File
@@ -4307,11 +4307,18 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
expect(parsedWorkflow.jobs.preflight.outputs.diff_base_revision).toBe(
"${{ steps.diff_base.outputs.sha }}",
);
expect(
parsedWorkflow.jobs.preflight.steps.find(
(step: WorkflowStep) => step.name === "Resolve exact diff base",
).run,
).toContain("--prefer-first-parent");
const diffBaseStep = parsedWorkflow.jobs.preflight.steps.find(
(step: WorkflowStep) => step.name === "Resolve exact diff base",
);
expect(diffBaseStep.run).toContain("--prefer-first-parent");
expect(diffBaseStep.env.DEFAULT_BRANCH).toBe("${{ github.event.repository.default_branch }}");
expect(diffBaseStep.env.GH_TOKEN).toBe(
"${{ github.event_name == 'workflow_dispatch' && !inputs.release_gate && github.token || '' }}",
);
expect(diffBaseStep.run).toContain(
'"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${head_sha}"',
);
expect(diffBaseStep.run).toContain("Could not resolve an exact diff base");
const securityDiffBase = parsedWorkflow.jobs["security-fast"].steps.find(
(step: WorkflowStep) => step.name === "Resolve security diff base",
).run;
@@ -4422,48 +4429,44 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
it("runs the suppression-baseline max-lines ratchet against the exact tested tree", () => {
const workflow = readCiWorkflow();
const checksFastSteps = workflow.jobs["checks-fast-core"].steps;
const checksFastJob = workflow.jobs["checks-fast-core"];
const checksFastSteps = checksFastJob.steps;
const checkout = checksFastSteps.find((step: WorkflowStep) => step.name === "Checkout");
const checksFastRun = checksFastSteps.find(
(step: WorkflowStep) => step.name === "Run ${{ matrix.task }} (${{ matrix.runtime }})",
);
const releaseGateMerge = checksFastSteps.find(
(step: WorkflowStep) => step.name === "Prepare release-gate max-lines merge tree",
);
const protocolManualBase = checksFastSteps.find(
(step: WorkflowStep) => step.name === "Resolve manual protocol base",
);
expect(
checksFastSteps.some((step: WorkflowStep) => step.name === "Resolve manual protocol base"),
).toBe(false);
expect(workflow.jobs["checks-fast-core"].permissions).toEqual({
contents: "read",
"pull-requests": "read",
});
expect(checksFastJob.env.CHECKOUT_BASE_SHA).toBe(
"${{ matrix.task == 'max-lines-ratchet' && needs.preflight.outputs.diff_base_revision || '' }}",
);
expect(checkout.run).toContain(
'fetch_refs+=("+${CHECKOUT_BASE_SHA}:refs/remotes/origin/ci-max-lines-base")',
);
expect(checkout.run).toContain('"${fetch_refs[@]}" || return 1');
expect(releaseGateMerge.if).toBe(
"matrix.task == 'max-lines-ratchet' && github.event_name == 'workflow_dispatch' && inputs.release_gate",
);
expect(checksFastRun.run).toContain("max-lines-ratchet)");
expect(checksFastRun.run).toContain('has_package_script "check:max-lines-ratchet"');
expect(checksFastRun.env.RATCHET_EVENT_BASE_SHA).toBe(
"${{ github.event_name == 'push' && github.event.before || '' }}",
);
expect(checksFastRun.env.RATCHET_PR_HEAD_SHA).toBe(
"${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}",
);
expect(checksFastRun.env.RATCHET_MANUAL_TARGET_SHA).toBe(
"${{ github.event_name == 'workflow_dispatch' && !inputs.release_gate && needs.preflight.outputs.checkout_revision || '' }}",
);
expect(checksFastRun.env.GH_TOKEN).toBe(
"${{ matrix.task == 'max-lines-ratchet' && github.token || '' }}",
);
expect(protocolManualBase.if).toBe(
"matrix.task == 'bundled-protocol' && github.event_name == 'workflow_dispatch' && !inputs.release_gate",
);
expect(protocolManualBase.env.GH_TOKEN).toBe("${{ github.token }}");
expect(protocolManualBase.run).toContain(
'"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${TARGET_SHA}"',
);
expect(protocolManualBase.run).toContain('echo "sha=${merge_base_sha}" >> "$GITHUB_OUTPUT"');
expect(checksFastRun.env.PROTOCOL_MANUAL_BASE_SHA).toBe(
"${{ steps.protocol_manual_base.outputs.sha }}",
expect(checksFastRun.env).not.toHaveProperty("RATCHET_EVENT_BASE_SHA");
expect(checksFastRun.env).not.toHaveProperty("RATCHET_MANUAL_TARGET_SHA");
expect(checksFastRun.env).not.toHaveProperty("GH_TOKEN");
expect(checksFastRun.env).not.toHaveProperty("PROTOCOL_MANUAL_BASE_SHA");
expect(checksFastRun.env.PROTOCOL_SINCE_BASE_SHA).toBe(
"${{ needs.preflight.outputs.diff_base_revision }}",
);
expect(releaseGateMerge.run).toContain(
'gh api --method GET "repos/${GITHUB_REPOSITORY}/pulls/${PULL_REQUEST_NUMBER}"',
@@ -4492,39 +4495,29 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
expect(releaseGateMerge.run).not.toContain(".base.sha");
expect(releaseGateMerge.run).toContain('git checkout --detach "$merge_sha"');
expect(releaseGateMerge.run).toContain(
'echo "RATCHET_RELEASE_BASE_SHA=${frozen_base_sha}" >> "$GITHUB_ENV"',
'echo "RATCHET_BASE_REF=${frozen_base_sha}" >> "$GITHUB_ENV"',
);
expect(releaseGateMerge.run).toContain(
'echo "RATCHET_RELEASE_MERGE_TREE=true" >> "$GITHUB_ENV"',
);
expect(
checksFastRun.run.match(
/timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags --depth=1 origin \\/gu,
),
).toHaveLength(4);
expect(checksFastRun.run).toContain('git ls-remote origin "refs/heads/${default_branch}"');
expect(checksFastRun.run).toContain(
'"repos/${GITHUB_REPOSITORY}/compare/${default_sha}...${RATCHET_MANUAL_TARGET_SHA}"',
);
expect(checksFastRun.run).toContain('PROTOCOL_SINCE_BASE_SHA="$PROTOCOL_MANUAL_BASE_SHA"');
expect(checksFastRun.run).not.toContain("PROTOCOL_MANUAL_BASE_SHA");
expect(checksFastRun.run).toContain(
'"+${PROTOCOL_SINCE_BASE_SHA}:refs/remotes/origin/protocol-since-base"',
);
expect(checksFastRun.run).toContain("--jq '.merge_base_commit.sha'");
expect(checksFastRun.run).toContain(
'"+${merge_base_sha}:refs/remotes/origin/ci-max-lines-base"',
);
expect(checksFastRun.run).toContain(
'if [[ "$base_sha" == "0000000000000000000000000000000000000000" ]]',
'base_ref="${RATCHET_BASE_REF:-refs/remotes/origin/ci-max-lines-base}"',
);
expect(checksFastRun.run).toContain('git cat-file -e "${base_ref}^{commit}"');
expect(checksFastRun.run).toContain(
"mapfile -t merge_parents < <(git cat-file -p HEAD | sed -n 's/^parent //p')",
);
expect(checksFastRun.run).toContain('"${#merge_parents[@]}" != "2"');
expect(checksFastRun.run).toContain('"${merge_parents[1]:-}" != "$RATCHET_PR_HEAD_SHA"');
expect(checksFastRun.run).toContain('"+${merge_base}:refs/remotes/origin/ci-max-lines-base"');
expect(checksFastRun.run).toContain('prepared_base="$(git rev-parse "$base_ref")"');
expect(checksFastRun.run).toContain('"${merge_parents[0]}" != "$prepared_base"');
expect(checksFastRun.run).not.toContain("ci-max-lines-target^");
expect(checksFastRun.run).toContain("unset GH_TOKEN");
expect(checksFastRun.run).not.toContain("resolve_manual_merge_base");
expect(checksFastRun.run).not.toContain("+${merge_base}:refs/remotes/origin/ci-max-lines-base");
expect(checksFastRun.run).toContain('pnpm check:max-lines-ratchet --base "$base_ref"');
expect(checksFastRun.run).toContain(
'if [[ "${RATCHET_RELEASE_MERGE_TREE:-}" == "true" ]]; then',
+3 -2
View File
@@ -645,13 +645,14 @@ suite.define(() => {
const overflowProof = await readBlobProof();
// Concurrent image fetches can resolve in any order. Find the real LRU
// rather than assuming that creation order matches transcript order.
expect(overflowProof.revoked).toHaveLength(1);
const evictedBlobUrl = expectDefined(
overflowProof.created.find((blobUrl) => blobUrl !== retainedRecentBlobUrl),
overflowProof.revoked.find((blobUrl) => blobUrl !== retainedRecentBlobUrl),
"evicted managed image Blob URL",
);
expect(overflowProof.created).toContain(evictedBlobUrl);
const evictedImageIndex = initialBlobUrls.indexOf(evictedBlobUrl);
expect(evictedImageIndex).toBeGreaterThanOrEqual(0);
expect(overflowProof.revoked).toContain(evictedBlobUrl);
expect(overflowProof.revoked).not.toContain(retainedRecentBlobUrl);
const evictedPath = new URL(
@@ -434,8 +434,8 @@ suite.define(() => {
await expect.poll(() => sidebarRows.count()).toBe(3);
const initialListCount = (await gateway.getRequests("sessions.list")).length;
await gateway.closeLatest(1006, "disconnect proof");
await gateway.deferNext("sessions.list");
await gateway.closeLatest(1006, "disconnect proof");
await sidebarRow.waitFor({ state: "visible" });
await captureUiProof(page, "sidebar-sessions-during-reconnect.png");