diff --git a/src/lib/shields/index.ts b/src/lib/shields/index.ts index e8e298b4b3a..a5ad383ae1d 100644 --- a/src/lib/shields/index.ts +++ b/src/lib/shields/index.ts @@ -4725,6 +4725,31 @@ function startFreshShieldsDownTimer(input: { } } +function persistIncompleteShieldsDownPosture( + sandboxName: string, + transition: ShieldsDownTransition, + timerAuthority: TimerMarker, + rollback: ShieldsDownRollbackResult, +): ShieldsDownTransition { + if (rollback.outcome !== "manual_intervention_required" || rollback.timerAuthorityRevoked) { + return transition; + } + + try { + assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "preparing"); + transition = { ...transition, phase: "active" }; + writeShieldsDownTransition(transition, "preparing"); + assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "active"); + } catch (transitionError) { + const transitionMessage = + transitionError instanceof Error ? transitionError.message : String(transitionError); + console.error( + ` CRITICAL: Could not persist the incomplete Shields down posture. Treat the config as mutable and recover it manually. ${transitionMessage}`, + ); + } + return transition; +} + function shieldsDownWithoutHostLock(sandboxName: string, opts: ShieldsDownOpts = {}): void { validateName(sandboxName, "sandbox name"); @@ -5150,25 +5175,12 @@ function shieldsDownWithoutHostLock(sandboxName: string, opts: ShieldsDownOpts = opts.allowLegacyHermesProtocol === true, protocol, ); - if ( - transition && - timerAuthority && - rollback.outcome === "manual_intervention_required" && - !rollback.timerAuthorityRevoked - ) { - try { - assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "preparing"); - transition = { ...transition, phase: "active" }; - writeShieldsDownTransition(transition, "preparing"); - assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "active"); - } catch (transitionError) { - const transitionMessage = - transitionError instanceof Error ? transitionError.message : String(transitionError); - console.error( - ` CRITICAL: Could not persist the incomplete Shields down posture. Treat the config as mutable and recover it manually. ${transitionMessage}`, - ); - } - } + transition = persistIncompleteShieldsDownPosture( + sandboxName, + transition, + timerAuthority, + rollback, + ); if (transition && rollback.timerAuthorityRevoked) { clearShieldsDownTransition(sandboxName, transition.processToken); } diff --git a/src/lib/shields/openclaw-transition.test.ts b/src/lib/shields/openclaw-transition.test.ts index 5808d326e52..4e1dc8988fd 100644 --- a/src/lib/shields/openclaw-transition.test.ts +++ b/src/lib/shields/openclaw-transition.test.ts @@ -196,7 +196,7 @@ describe("OpenClaw shields top-config transaction", () => { ); shields = requireSource(INDEX_MODULE); - }); + }, 30_000); afterEach(() => { for (const spy of spies) spy.mockRestore(); diff --git a/test/dcode-base-image-workflow.test.ts b/test/dcode-base-image-workflow.test.ts index 58fabf45c8f..fa1dfc748f2 100644 --- a/test/dcode-base-image-workflow.test.ts +++ b/test/dcode-base-image-workflow.test.ts @@ -5,6 +5,14 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import YAML from "yaml"; + +type Step = { + env?: Record; + if?: string; + name?: string; + run?: string; +}; const repoRoot = path.resolve(import.meta.dirname, ".."); const baseDockerfiles = [ @@ -30,4 +38,49 @@ describe("base-image dependency contracts", () => { expect(source, dockerfile).toMatch(/^FROM\s+\S+@sha256:[0-9a-f]{64}\s*$/m); } }); + + it("executes dos2unix from each Deep Agents Code platform image before manifest publication (#8870)", () => { + const action = YAML.parse( + fs.readFileSync( + path.join(repoRoot, ".github", "actions", "build-base-image-platform", "action.yaml"), + "utf8", + ), + ) as { runs?: { steps?: Step[] } }; + const steps = action.runs?.steps ?? []; + const validate = + steps.find( + (candidate) => candidate.name === "Validate Deep Agents Code dos2unix executable", + ) ?? + (() => { + throw new Error("Base-image platform action is missing the dos2unix validation"); + })(); + const buildIndex = steps.findIndex( + (candidate) => candidate.name === "Build and push platform digest", + ); + const validateIndex = steps.indexOf(validate); + const exportIndex = steps.findIndex((candidate) => candidate.name === "Export platform digest"); + + expect(validate.if).toBe("${{ inputs.agent == 'langchain-deepagents-code' }}"); + expect(validate.env).toMatchObject({ + DIGEST: "${{ steps.build.outputs.digest }}", + IMAGE: "${{ inputs.registry }}/${{ inputs.image }}", + PLATFORM: "${{ inputs.platform }}", + }); + expect(validate.run).toContain('reference="${IMAGE}@${DIGEST}"'); + expect(validate.run).toContain("^sha256:[0-9a-f]{64}$"); + expect(validate.run).toContain('docker run --rm --platform "$PLATFORM"'); + expect(validate.run).toContain("--network none"); + expect(validate.run).toContain("--cap-drop ALL"); + expect(validate.run).toContain("--security-opt no-new-privileges"); + expect(validate.run).toContain("--read-only"); + expect(validate.run).toContain("--user 999:999"); + expect(validate.run).toContain("test -x /usr/bin/dos2unix"); + expect(validate.run).toContain('test "$(command -v dos2unix)" = /usr/bin/dos2unix'); + expect(validate.run).toContain("dos2unix --version"); + expect(buildIndex).toBeGreaterThanOrEqual(0); + expect(validateIndex).toBeGreaterThanOrEqual(0); + expect(exportIndex).toBeGreaterThanOrEqual(0); + expect(validateIndex).toBeGreaterThan(buildIndex); + expect(validateIndex).toBeLessThan(exportIndex); + }); }); diff --git a/test/helpers/vitest-watch-triggers.ts b/test/helpers/vitest-watch-triggers.ts index b5b338f59d7..1809d49f86d 100644 --- a/test/helpers/vitest-watch-triggers.ts +++ b/test/helpers/vitest-watch-triggers.ts @@ -105,6 +105,13 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [ "test/dcode-base-image-workflow.test.ts", ), }, + { + pattern: /(?:^|\/)\.github\/actions\/build-base-image-platform\/action\.yaml$/, + testsToRun: runTests( + "test/dcode-base-image-workflow.test.ts", + "test/openclaw-dependency-review.test.ts", + ), + }, { pattern: /(?:^|\/)scripts\/checks\/validate-managed-base-index\.sh$/, testsToRun: runTests("test/validate-managed-base-index.test.ts"), diff --git a/test/managed-image-publication-workflow.test.ts b/test/managed-image-publication-workflow.test.ts index e5456ef2525..6fe551bcfbe 100644 --- a/test/managed-image-publication-workflow.test.ts +++ b/test/managed-image-publication-workflow.test.ts @@ -458,43 +458,6 @@ describe("complete managed-image publication workflow", () => { } }); - it("executes dos2unix from each Deep Agents Code platform image before manifest publication (#8870)", () => { - const action = YAML.parse( - fs.readFileSync( - path.join(repoRoot, ".github", "actions", "build-base-image-platform", "action.yaml"), - "utf8", - ), - ) as { runs?: { steps?: Step[] } }; - const steps = action.runs?.steps ?? []; - const validate = required( - steps.find((candidate) => candidate.name === "Validate Deep Agents Code dos2unix executable"), - "base-image platform action is missing the Deep Agents Code dos2unix validation", - ); - const buildIndex = steps.findIndex( - (candidate) => candidate.name === "Build and push platform digest", - ); - const validateIndex = steps.indexOf(validate); - const exportIndex = steps.findIndex((candidate) => candidate.name === "Export platform digest"); - - expect(validate.if).toBe("${{ inputs.agent == 'langchain-deepagents-code' }}"); - expect(validate.env).toMatchObject({ - DIGEST: "${{ steps.build.outputs.digest }}", - IMAGE: "${{ inputs.registry }}/${{ inputs.image }}", - PLATFORM: "${{ inputs.platform }}", - }); - expect(validate.run).toContain('reference="${IMAGE}@${DIGEST}"'); - expect(validate.run).toContain('docker run --rm --platform "$PLATFORM"'); - expect(validate.run).toContain("--network none"); - expect(validate.run).toContain("--cap-drop ALL"); - expect(validate.run).toContain("--security-opt no-new-privileges"); - expect(validate.run).toContain("--read-only"); - expect(validate.run).toContain("--user 999:999"); - expect(validate.run).toContain("test -x /usr/bin/dos2unix"); - expect(validate.run).toContain("dos2unix --version"); - expect(validateIndex).toBeGreaterThan(buildIndex); - expect(validateIndex).toBeLessThan(exportIndex); - }); - it("builds and exercises every shipped agent from an exact PR image before merge (#7744)", () => { const workflow = readWorkflow("managed-images.yaml"); const reviewedAudit = managedPrReviewedAudit(workflow); diff --git a/test/vitest-watch-triggers.test.ts b/test/vitest-watch-triggers.test.ts index 6a09d9d808b..eac2048b291 100644 --- a/test/vitest-watch-triggers.test.ts +++ b/test/vitest-watch-triggers.test.ts @@ -144,6 +144,10 @@ describe("Vitest opaque-input watch triggers", () => { "test/managed-image-publication-workflow.test.ts", "test/dcode-base-image-workflow.test.ts", ]); + expect(triggeredBy(".github/actions/build-base-image-platform/action.yaml")).toEqual([ + "test/dcode-base-image-workflow.test.ts", + "test/openclaw-dependency-review.test.ts", + ]); expect(triggeredBy("scripts/checks/validate-managed-base-index.sh")).toEqual([ "test/validate-managed-base-index.test.ts", ]);