mirror of
https://github.com/NVIDIA/NemoClaw.git
synced 2026-07-03 03:37:16 +00:00
refactor(ci): centralize E2E artifact uploads (#6121)
<!-- markdownlint-disable MD041 --> ## Summary This third PR in the CI cleanup stack centralizes all 73 E2E artifact-upload blocks behind one immutable shared action. It preserves every effective artifact name, path, upload policy, caller condition, and step position while removing 670 net lines, including 411 lines from the workflow itself. Stacked on #6118. ## Changes - Add one shared uploader with the existing full-SHA `actions/upload-artifact` dependency, caller-independent `always()`, hidden-file exclusion, missing-file tolerance, and 14-day retention. - Pin all 73 callers to the action-only commit `7768e15eb90d3ee2d33432f481dfe8747e4f6d57`; 64 use target-derived defaults and nine retain exact explicit name/path contracts. - Bind the immutable action reference and reviewed SHA-256 content digest in one provenance manifest. - Replace repeated upload assertions with one strict validator covering exact action bytes/schema/policy, caller inventory, defaults, exceptions, ordering, local/direct-action rejection, and immutable reference enforcement. - Remove 821 lines of redundant per-job validator logic and add focused mutation coverage for every consolidated invariant. - Preserve the normalized full-workflow AST exactly after expanding action defaults and policy; zero non-upload workflow mismatches. - Validate the default path on hosted run `28505657814`: GitHub resolved the immutable action, applied the target-derived name/path and 14-day policy, and uploaded artifact `8004917898` successfully. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [x] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [ ] Docs updated for user-facing behavior changes - [x] Docs not applicable — justification: Internal CI workflow/action/validator refactor only; no user-facing commands, configuration, APIs, policies, matrices, or supported behavior changes. - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [x] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Independent review confirmed exact 73-call behavior parity, immutable same-repository action provenance, caller and inner `always()` semantics, strict 64-default/9-exception mapping, and no lost security invariant or permission expansion. - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Verification - [x] PR description includes the DCO sign-off declaration and every commit appears as `Verified` in GitHub - [x] Git hooks passed during commit and push, or `npx prek run --from-ref main --to-ref HEAD` passes - [x] Targeted tests pass for changed behavior - [ ] Full `npm test` passes (broad runtime changes only) - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) --- Signed-off-by: Carlos Villela <cvillela@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a shared upload action for E2E-generated artifacts, with consistent defaults and longer retention. * Expanded workflow checks to validate artifact uploads through the new shared action across E2E jobs. * **Bug Fixes** * Standardized artifact publishing behavior across workflows. * Tightened validation so E2E jobs follow the expected upload pattern and avoid unsupported overrides. * **Tests** * Updated and added workflow-boundary coverage for the new artifact upload flow and its expected defaults. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
de1089eb30
commit
0cc5d7668e
13 changed files with 649 additions and 1319 deletions
28
.github/actions/upload-e2e-artifacts/action.yaml
vendored
Normal file
28
.github/actions/upload-e2e-artifacts/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: upload-e2e-artifacts
|
||||
description: Upload the artifacts produced by an E2E target.
|
||||
|
||||
inputs:
|
||||
name:
|
||||
description: Artifact name. Defaults to the current E2E target.
|
||||
required: false
|
||||
default: ""
|
||||
path:
|
||||
description: Artifact path. Defaults to the current E2E target's artifact directory.
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Upload E2E artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ inputs.name != '' && inputs.name || format('e2e-{0}', env.E2E_TARGET_ID) }}
|
||||
path: ${{ inputs.path != '' && inputs.path || format('e2e-artifacts/live/{0}/', env.E2E_TARGET_ID) }}
|
||||
include-hidden-files: false
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
557
.github/workflows/e2e.yaml
vendored
557
.github/workflows/e2e.yaml
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -102,7 +102,7 @@ describe("e2e workflow boundary", () => {
|
|||
|
||||
try {
|
||||
expect(validateE2eWorkflowBoundary(workflowPath)).toContain(
|
||||
"artifact upload path must not include /tmp/",
|
||||
"openclaw-inference-switch upload-e2e-artifacts must preserve its explicit name/path contract",
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
|
|
@ -985,8 +985,8 @@ jobs:
|
|||
"live job env must not include NVIDIA_INFERENCE_API_KEY",
|
||||
"step 'Run live E2E tests' run script must not interpolate dispatch inputs directly",
|
||||
"live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets",
|
||||
"artifact upload must set include-hidden-files: false",
|
||||
"upload-artifact action must be pinned to a full commit SHA",
|
||||
"live must not invoke actions/upload-artifact directly",
|
||||
"live must use upload-e2e-artifacts exactly once",
|
||||
"openshell-version-pin job must use the shared jobs selector condition",
|
||||
"network-policy job env must not include NVIDIA_INFERENCE_API_KEY",
|
||||
"network-policy step 'Install OpenShell' env must not include GITHUB_TOKEN",
|
||||
|
|
@ -1080,8 +1080,8 @@ jobs:
|
|||
"snapshot-commands job must not set DOCKER_CONFIG at job level",
|
||||
"snapshot-commands checkout step must set persist-credentials=false",
|
||||
"snapshot-commands job env must not include NVIDIA_INFERENCE_API_KEY",
|
||||
"snapshot-commands artifact upload must set include-hidden-files: false",
|
||||
"artifact upload path must include e2e-artifacts/live/snapshot-commands/",
|
||||
"snapshot-commands upload-e2e-artifacts invocation must not override its contract",
|
||||
"snapshot-commands upload-e2e-artifacts must use the action defaults",
|
||||
"step 'Run snapshot commands live test' run script must include test/e2e/live/snapshot-commands.test.ts",
|
||||
]),
|
||||
);
|
||||
|
|
@ -1235,10 +1235,8 @@ jobs:
|
|||
"channels-stop-start step must receive NVIDIA_INFERENCE_API_KEY from secrets",
|
||||
"channels-stop-start step must set the fake Telegram token",
|
||||
"step 'Run channels stop/start live test' run script must include test/e2e/live/channels-stop-start.test.ts",
|
||||
"channels-stop-start upload-artifact action must be pinned to a full commit SHA",
|
||||
"channels-stop-start artifact upload name must include matrix.agent",
|
||||
"channels-stop-start artifact upload must set include-hidden-files: false",
|
||||
"channels-stop-start artifact upload retention-days must be 14",
|
||||
"channels-stop-start must not invoke actions/upload-artifact directly",
|
||||
"channels-stop-start must use upload-e2e-artifacts exactly once",
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
|
|
@ -1388,8 +1386,8 @@ jobs:
|
|||
"diagnostics step 'Authenticate to Docker Hub' env must not include DOCKERHUB_TOKEN",
|
||||
"diagnostics step 'Authenticate to Docker Hub' must not authenticate or interpolate Docker Hub secrets",
|
||||
"step 'Run diagnostics live test' run script must not interpolate dispatch inputs directly",
|
||||
"diagnostics artifact upload must set include-hidden-files: false",
|
||||
"diagnostics artifact upload retention-days must be 14",
|
||||
"diagnostics upload-e2e-artifacts invocation must not override its contract",
|
||||
"diagnostics upload-e2e-artifacts must use the action defaults",
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { UPLOAD_E2E_ARTIFACTS_ACTION } from "../../../tools/e2e/upload-e2e-artifacts-workflow-boundary.mts";
|
||||
import {
|
||||
evaluateE2eWorkflowDispatchSelectors,
|
||||
readFreeStandingJobsInventory,
|
||||
|
|
@ -92,13 +93,8 @@ describe("OpenShell gateway auth contract workflow boundary", () => {
|
|||
|
||||
const upload = namedStep(job, "Upload OpenShell gateway auth contract artifacts");
|
||||
expect(upload?.if).toBe("always()");
|
||||
expect(upload?.with).toMatchObject({
|
||||
name: "e2e-openshell-gateway-auth-contract",
|
||||
path: "e2e-artifacts/live/openshell-gateway-auth-contract/",
|
||||
"include-hidden-files": false,
|
||||
"if-no-files-found": "ignore",
|
||||
"retention-days": 14,
|
||||
});
|
||||
expect(upload?.uses).toBe(UPLOAD_E2E_ARTIFACTS_ACTION);
|
||||
expect(upload?.with).toBeUndefined();
|
||||
});
|
||||
|
||||
it("waits for the auth contract in every aggregate result job", () => {
|
||||
|
|
|
|||
|
|
@ -128,11 +128,8 @@ describe("spark install workflow boundary", () => {
|
|||
"spark-install live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets",
|
||||
"step 'Run Spark install live test' run script must include set -euo pipefail",
|
||||
"step 'Run Spark install live test' run script must include test/e2e/live/spark-install.test.ts",
|
||||
"spark-install artifact upload name must be stable",
|
||||
"artifact upload path must include e2e-artifacts/live/spark-install/",
|
||||
"spark-install artifact upload must set include-hidden-files: false",
|
||||
"spark-install artifact upload must ignore missing fixture artifacts",
|
||||
"spark-install artifact upload retention-days must be 14",
|
||||
"spark-install upload-e2e-artifacts invocation must not override its contract",
|
||||
"spark-install upload-e2e-artifacts must use the action defaults",
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ describe("tunnel lifecycle workflow boundary", () => {
|
|||
"step 'Install and verify cloudflared prerequisite' run script must include sudo dpkg -i",
|
||||
"step 'Install and verify cloudflared prerequisite' run script must include cloudflared version ${CLOUDFLARED_VERSION}",
|
||||
"tunnel-lifecycle live E2E step must not run cloudflared APT installation with NVIDIA_INFERENCE_API_KEY in scope",
|
||||
"artifact upload path must include e2e-artifacts/live/tunnel-lifecycle/",
|
||||
"tunnel-lifecycle artifact upload must set include-hidden-files: false",
|
||||
"tunnel-lifecycle upload-e2e-artifacts invocation must not override its contract",
|
||||
"tunnel-lifecycle upload-e2e-artifacts must use the action defaults",
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
|
|
|
|||
183
test/e2e/support/upload-e2e-artifacts-workflow-boundary.test.ts
Normal file
183
test/e2e/support/upload-e2e-artifacts-workflow-boundary.test.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import YAML from "yaml";
|
||||
|
||||
import {
|
||||
UPLOAD_E2E_ARTIFACTS_ACTION,
|
||||
validateUploadE2eArtifactsAction,
|
||||
validateUploadE2eArtifactsInvocations,
|
||||
} from "../../../tools/e2e/upload-e2e-artifacts-workflow-boundary.mts";
|
||||
import { readWorkflow } from "../../helpers/e2e-workflow-contract";
|
||||
|
||||
const ACTION_PATH = join(
|
||||
process.cwd(),
|
||||
".github",
|
||||
"actions",
|
||||
"upload-e2e-artifacts",
|
||||
"action.yaml",
|
||||
);
|
||||
const LOCAL_UPLOAD_ACTION = "./.github/actions/upload-e2e-artifacts";
|
||||
const DIRECT_UPLOAD_ACTION = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a";
|
||||
|
||||
type MutableStep = Record<string, unknown> & {
|
||||
name?: string;
|
||||
if?: string;
|
||||
uses?: string;
|
||||
with?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type MutableJob = Record<string, unknown> & {
|
||||
env?: Record<string, unknown>;
|
||||
steps?: MutableStep[];
|
||||
};
|
||||
|
||||
type MutableWorkflow = {
|
||||
jobs: Record<string, MutableJob>;
|
||||
};
|
||||
|
||||
type MutableAction = {
|
||||
runs: {
|
||||
steps: MutableStep[];
|
||||
};
|
||||
};
|
||||
|
||||
function mutableWorkflow(): MutableWorkflow {
|
||||
return readWorkflow() as unknown as MutableWorkflow;
|
||||
}
|
||||
|
||||
function uploadStep(job: MutableJob): MutableStep {
|
||||
const upload = job.steps?.find((step) => step.uses === UPLOAD_E2E_ARTIFACTS_ACTION);
|
||||
expect(upload).toBeDefined();
|
||||
return upload!;
|
||||
}
|
||||
|
||||
function validateActionSourceMutation(mutate: (source: string) => string): string[] {
|
||||
const directory = mkdtempSync(join(tmpdir(), "nemoclaw-upload-e2e-artifacts-"));
|
||||
const actionPath = join(directory, "action.yaml");
|
||||
try {
|
||||
writeFileSync(actionPath, mutate(readFileSync(ACTION_PATH, "utf8")));
|
||||
return validateUploadE2eArtifactsAction(actionPath);
|
||||
} finally {
|
||||
rmSync(directory, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function validateActionMutation(mutate: (action: MutableAction) => void): string[] {
|
||||
return validateActionSourceMutation((source) => {
|
||||
const action = YAML.parse(source) as MutableAction;
|
||||
mutate(action);
|
||||
return YAML.stringify(action);
|
||||
});
|
||||
}
|
||||
|
||||
describe("upload-e2e-artifacts workflow boundary", () => {
|
||||
it("binds one canonical uploader to all 73 E2E execution jobs", () => {
|
||||
expect(validateUploadE2eArtifactsAction()).toEqual([]);
|
||||
expect(validateUploadE2eArtifactsInvocations(readWorkflow())).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects semantic-neutral action byte drift from the immutable provenance", () => {
|
||||
expect(validateActionSourceMutation((source) => `${source}# unreviewed drift\n`)).toEqual([
|
||||
"upload-e2e-artifacts content must match the action reviewed at its immutable commit pin",
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects action upload-policy and inner always drift", () => {
|
||||
const policyErrors = validateActionMutation((action) => {
|
||||
action.runs.steps[0].with!["retention-days"] = 7;
|
||||
});
|
||||
expect(policyErrors).toContain(
|
||||
"upload-e2e-artifacts must preserve artifact defaults, hidden-file policy, missing-file behavior, and retention",
|
||||
);
|
||||
|
||||
const alwaysErrors = validateActionMutation((action) => {
|
||||
action.runs.steps[0].if = "${{ success() }}";
|
||||
});
|
||||
expect(alwaysErrors).toContain("upload-e2e-artifacts inner step must run with always()");
|
||||
});
|
||||
|
||||
it("rejects checkout-local, direct, and unreviewed remote upload actions", () => {
|
||||
const workflow = mutableWorkflow();
|
||||
uploadStep(workflow.jobs["inference-routing"]).uses = LOCAL_UPLOAD_ACTION;
|
||||
uploadStep(workflow.jobs["network-policy"]).uses = DIRECT_UPLOAD_ACTION;
|
||||
uploadStep(workflow.jobs["docs-validation"]).uses =
|
||||
"NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@main";
|
||||
|
||||
expect(validateUploadE2eArtifactsInvocations(workflow)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"inference-routing must not load upload-e2e-artifacts from the target checkout",
|
||||
"inference-routing must use upload-e2e-artifacts exactly once",
|
||||
"network-policy must not invoke actions/upload-artifact directly",
|
||||
"network-policy must use upload-e2e-artifacts exactly once",
|
||||
"docs-validation must use the reviewed immutable upload-e2e-artifacts reference",
|
||||
"docs-validation must use upload-e2e-artifacts exactly once",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects missing and duplicate shared upload invocations", () => {
|
||||
const workflow = mutableWorkflow();
|
||||
const missingJob = workflow.jobs["openshell-version-pin"];
|
||||
missingJob.steps = missingJob.steps!.filter(
|
||||
(step) => step.uses !== UPLOAD_E2E_ARTIFACTS_ACTION,
|
||||
);
|
||||
const duplicateJob = workflow.jobs["cloud-inference"];
|
||||
duplicateJob.steps!.push({ ...uploadStep(duplicateJob) });
|
||||
|
||||
expect(validateUploadE2eArtifactsInvocations(workflow)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"openshell-version-pin must use upload-e2e-artifacts exactly once",
|
||||
"cloud-inference must use upload-e2e-artifacts exactly once",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects default, explicit-exception, caller-key, and caller-if drift", () => {
|
||||
const workflow = mutableWorkflow();
|
||||
const defaultJob = workflow.jobs["credential-migration"];
|
||||
uploadStep(defaultJob).with = { name: "e2e-credential-migration" };
|
||||
defaultJob.env!.E2E_TARGET_ID = "not a selector id";
|
||||
|
||||
uploadStep(workflow.jobs["hermes-slack"]).with!.path = "e2e-artifacts/live/hermes-slack/";
|
||||
uploadStep(workflow.jobs["gpu-e2e"]).if = "success()";
|
||||
uploadStep(workflow.jobs["docs-validation"]).env = { UNEXPECTED: "1" };
|
||||
const orderedJob = workflow.jobs["network-policy"];
|
||||
const orderedUpload = uploadStep(orderedJob);
|
||||
orderedJob.steps!.splice(orderedJob.steps!.indexOf(orderedUpload), 1);
|
||||
orderedJob.steps!.unshift(orderedUpload);
|
||||
|
||||
expect(validateUploadE2eArtifactsInvocations(workflow)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"credential-migration upload-e2e-artifacts invocation must not override its contract",
|
||||
"credential-migration upload-e2e-artifacts must use the action defaults",
|
||||
"credential-migration default upload caller must declare a valid E2E_TARGET_ID",
|
||||
"hermes-slack upload-e2e-artifacts must preserve its explicit name/path contract",
|
||||
"gpu-e2e upload-e2e-artifacts invocation must run with always()",
|
||||
"docs-validation upload-e2e-artifacts invocation must not override its contract",
|
||||
"network-policy upload-e2e-artifacts invocation must follow artifact producers and precede only Docker auth cleanup",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects execution-inventory drift even when its upload disappears with it", () => {
|
||||
const workflow = mutableWorkflow();
|
||||
const removedJob = workflow.jobs["credential-sanitization"];
|
||||
delete removedJob.env!.E2E_JOB;
|
||||
removedJob.steps = removedJob.steps!.filter(
|
||||
(step) => step.uses !== UPLOAD_E2E_ARTIFACTS_ACTION,
|
||||
);
|
||||
|
||||
expect(validateUploadE2eArtifactsInvocations(workflow)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"upload-e2e-artifacts must cover exactly 73 live and E2E_JOB execution jobs",
|
||||
"upload-e2e-artifacts must keep exactly 64 default callers",
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -113,42 +113,6 @@ export function validateDocsValidationWorkflow(workflow: DocsValidationWorkflow)
|
|||
requireRunContains(errors, run, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, run, "test/e2e/live/docs-validation.test.ts");
|
||||
|
||||
const upload = findStep(job, "Upload docs validation artifacts");
|
||||
requireEqual(errors, upload.if, "always()", `${JOB_NAME} artifact upload must always run`);
|
||||
if (!/^actions\/upload-artifact@[0-9a-f]{40}$/u.test(upload.uses ?? "")) {
|
||||
errors.push(`${JOB_NAME} artifact upload must pin a full action SHA`);
|
||||
}
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.name,
|
||||
"e2e-docs-validation",
|
||||
`${JOB_NAME} artifact name must remain stable`,
|
||||
);
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.path,
|
||||
"e2e-artifacts/live/docs-validation/",
|
||||
`${JOB_NAME} must upload docs-validation artifacts`,
|
||||
);
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.["include-hidden-files"],
|
||||
false,
|
||||
`${JOB_NAME} artifact upload must exclude hidden files`,
|
||||
);
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.["if-no-files-found"],
|
||||
"ignore",
|
||||
`${JOB_NAME} artifact upload must tolerate missing failure artifacts`,
|
||||
);
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.["retention-days"],
|
||||
14,
|
||||
`${JOB_NAME} artifact retention must remain 14 days`,
|
||||
);
|
||||
|
||||
const reportNeeds = workflow.jobs["report-to-pr"]?.needs;
|
||||
if (!Array.isArray(reportNeeds) || !reportNeeds.includes(JOB_NAME)) {
|
||||
errors.push(`report-to-pr must wait for ${JOB_NAME}`);
|
||||
|
|
|
|||
|
|
@ -103,17 +103,6 @@ export function validateHermesDashboardWorkflow(workflow: HermesDashboardWorkflo
|
|||
`${JOB_NAME} must pass the inference key through step env`,
|
||||
);
|
||||
|
||||
const upload = findStep(job, "Upload Hermes dashboard live Vitest artifacts");
|
||||
if (!/^actions\/upload-artifact@[0-9a-f]{40}$/u.test(upload.uses ?? "")) {
|
||||
errors.push(`${JOB_NAME} artifact upload must pin a full action SHA`);
|
||||
}
|
||||
requireEqual(
|
||||
errors,
|
||||
upload.with?.path,
|
||||
"e2e-artifacts/live/hermes-dashboard/",
|
||||
`${JOB_NAME} must upload its dashboard artifacts`,
|
||||
);
|
||||
|
||||
const reportNeeds = workflow.jobs["report-to-pr"]?.needs;
|
||||
if (!Array.isArray(reportNeeds) || !reportNeeds.includes(JOB_NAME)) {
|
||||
errors.push(`report-to-pr must wait for ${JOB_NAME}`);
|
||||
|
|
|
|||
|
|
@ -158,15 +158,6 @@ export function validateSandboxOperationsWorkflow(workflow: {
|
|||
requireRunContains(errors, run, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, run, "test/e2e/live/sandbox-operations.test.ts");
|
||||
|
||||
const upload = findStep(job, "Upload sandbox operations artifacts");
|
||||
if (upload.if !== "always()") errors.push(`${JOB_NAME} artifact upload must always run`);
|
||||
if (upload.with?.path !== "e2e-artifacts/live/sandbox-operations/") {
|
||||
errors.push(`${JOB_NAME} must upload sandbox operations artifacts`);
|
||||
}
|
||||
if (upload.with?.["include-hidden-files"] !== false) {
|
||||
errors.push(`${JOB_NAME} artifact upload must exclude hidden files`);
|
||||
}
|
||||
|
||||
const cleanup = findStep(job, "Clean up Docker auth");
|
||||
if (cleanup.if !== "always()") errors.push(`${JOB_NAME} Docker auth cleanup must always run`);
|
||||
|
||||
|
|
|
|||
|
|
@ -152,24 +152,6 @@ export function validateSecurityPostureWorkflow(workflow: WorkflowRecord): strin
|
|||
requireRunContains(errors, run, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, run, '"${{ matrix.test_file }}"');
|
||||
|
||||
const upload = namedStep(jobSteps, "Upload security posture artifacts");
|
||||
if (upload.if !== "always()") errors.push(`${JOB_NAME} artifact upload must always run`);
|
||||
if (record(upload.with).name !== "e2e-security-posture-${{ matrix.agent }}") {
|
||||
errors.push(`${JOB_NAME} artifact name must identify the agent matrix leg`);
|
||||
}
|
||||
if (record(upload.with).path !== "e2e-artifacts/live/security-posture-${{ matrix.agent }}/") {
|
||||
errors.push(`${JOB_NAME} artifact path must identify the agent matrix leg`);
|
||||
}
|
||||
if (record(upload.with)["include-hidden-files"] !== false) {
|
||||
errors.push(`${JOB_NAME} artifact upload must exclude hidden files`);
|
||||
}
|
||||
if (record(upload.with)["if-no-files-found"] !== "ignore") {
|
||||
errors.push(`${JOB_NAME} artifact upload must ignore missing files`);
|
||||
}
|
||||
if (record(upload.with)["retention-days"] !== 14) {
|
||||
errors.push(`${JOB_NAME} artifact retention must be 14 days`);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
|
|
|||
347
tools/e2e/upload-e2e-artifacts-workflow-boundary.mts
Normal file
347
tools/e2e/upload-e2e-artifacts-workflow-boundary.mts
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import YAML from "yaml";
|
||||
|
||||
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const DEFAULT_ACTION_PATH = join(
|
||||
REPO_ROOT,
|
||||
".github",
|
||||
"actions",
|
||||
"upload-e2e-artifacts",
|
||||
"action.yaml",
|
||||
);
|
||||
|
||||
export const UPLOAD_E2E_ARTIFACTS_ACTION_PROVENANCE = {
|
||||
reference:
|
||||
"NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57",
|
||||
contentSha256: "8f6f71a0e6d71d85418fa88c2b26a4d601f568bdcaae20aca4085ae423c5044b",
|
||||
} as const;
|
||||
|
||||
export const UPLOAD_E2E_ARTIFACTS_ACTION = UPLOAD_E2E_ARTIFACTS_ACTION_PROVENANCE.reference;
|
||||
|
||||
const CHECKOUT_LOCAL_UPLOAD_E2E_ARTIFACTS_ACTION = "./.github/actions/upload-e2e-artifacts";
|
||||
const UPLOAD_E2E_ARTIFACTS_ACTION_PREFIX = "NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@";
|
||||
const UPLOAD_ARTIFACT_ACTION = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a";
|
||||
const UPLOAD_ARTIFACT_ACTION_PREFIX = "actions/upload-artifact@";
|
||||
const INNER_ALWAYS = "${{ always() }}";
|
||||
const CALLER_ALWAYS = "always()";
|
||||
const TARGET_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
||||
const EXPECTED_UPLOAD_JOB_COUNT = 73;
|
||||
const EXPECTED_DEFAULT_CALLER_COUNT = 64;
|
||||
|
||||
type WorkflowRecord = Record<string, unknown>;
|
||||
type WorkflowStep = WorkflowRecord & {
|
||||
name?: string;
|
||||
if?: string;
|
||||
uses?: string;
|
||||
with?: WorkflowRecord;
|
||||
};
|
||||
|
||||
type ExplicitUploadContract = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
const EXPLICIT_UPLOAD_CONTRACTS = new Map<string, ExplicitUploadContract>([
|
||||
[
|
||||
"live",
|
||||
{
|
||||
name: "e2e-${{ matrix.id }}",
|
||||
path: [
|
||||
"e2e-artifacts/live/${{ matrix.id }}/run-plan.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/target.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/target-result.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/environment.result.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/onboarding.result.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/state-validation.result.json",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/actions/",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/logs/",
|
||||
"e2e-artifacts/live/${{ matrix.id }}/shell/",
|
||||
"",
|
||||
].join("\n"),
|
||||
},
|
||||
],
|
||||
[
|
||||
"skill-agent",
|
||||
{
|
||||
name: "e2e-skill-agent",
|
||||
path: [
|
||||
"e2e-artifacts/live/skill-agent/*/artifact-summary.json",
|
||||
"e2e-artifacts/live/skill-agent/*/cleanup.json",
|
||||
"e2e-artifacts/live/skill-agent/*/cleanup-skill-agent-summary.json",
|
||||
"e2e-artifacts/live/skill-agent/*/target.json",
|
||||
"e2e-artifacts/live/skill-agent/*/target-result.json",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.result.json",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.stdout.txt",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.stderr.txt",
|
||||
"",
|
||||
].join("\n"),
|
||||
},
|
||||
],
|
||||
[
|
||||
"hermes-inference-switch",
|
||||
{
|
||||
name: "e2e-hermes-inference-switch-${{ matrix.mode }}",
|
||||
path: "e2e-artifacts/live/hermes-inference-switch/${{ matrix.mode }}/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"hermes-slack",
|
||||
{
|
||||
name: "e2e-hermes-slack",
|
||||
path: "e2e-artifacts/live/hermes-slack-e2e/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"shields-config",
|
||||
{
|
||||
name: "e2e-shields-config",
|
||||
path: "e2e-artifacts/live/shields-config/\n",
|
||||
},
|
||||
],
|
||||
[
|
||||
"security-posture",
|
||||
{
|
||||
name: "e2e-security-posture-${{ matrix.agent }}",
|
||||
path: "e2e-artifacts/live/security-posture-${{ matrix.agent }}/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"openclaw-inference-switch",
|
||||
{
|
||||
name: "e2e-openclaw-inference-switch-${{ matrix.mode }}",
|
||||
path: "e2e-artifacts/live/openclaw-inference-switch/${{ matrix.mode }}/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"bedrock-runtime-compatible-anthropic",
|
||||
{
|
||||
name: "e2e-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}",
|
||||
path: "e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }}/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"channels-stop-start",
|
||||
{
|
||||
name: "e2e-channels-stop-start-${{ matrix.agent }}",
|
||||
path: "e2e-artifacts/live/channels-stop-start/${{ matrix.agent }}/",
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
const EXPECTED_ACTION_INPUTS = {
|
||||
name: {
|
||||
description: "Artifact name. Defaults to the current E2E target.",
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
path: {
|
||||
description: "Artifact path. Defaults to the current E2E target's artifact directory.",
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED_UPLOAD_POLICY = {
|
||||
name: "${{ inputs.name != '' && inputs.name || format('e2e-{0}', env.E2E_TARGET_ID) }}",
|
||||
path: "${{ inputs.path != '' && inputs.path || format('e2e-artifacts/live/{0}/', env.E2E_TARGET_ID) }}",
|
||||
"include-hidden-files": false,
|
||||
"if-no-files-found": "ignore",
|
||||
"retention-days": 14,
|
||||
};
|
||||
|
||||
function record(value: unknown): WorkflowRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as WorkflowRecord)
|
||||
: {};
|
||||
}
|
||||
|
||||
function steps(value: unknown): WorkflowStep[] {
|
||||
return Array.isArray(value) ? (value as WorkflowStep[]) : [];
|
||||
}
|
||||
|
||||
function sortedKeys(value: WorkflowRecord): string[] {
|
||||
return Object.keys(value).sort();
|
||||
}
|
||||
|
||||
export function validateUploadE2eArtifactsAction(actionPath = DEFAULT_ACTION_PATH): string[] {
|
||||
const source = readFileSync(actionPath, "utf8");
|
||||
const action = record(YAML.parse(source));
|
||||
const errors: string[] = [];
|
||||
|
||||
if (
|
||||
createHash("sha256").update(source).digest("hex") !==
|
||||
UPLOAD_E2E_ARTIFACTS_ACTION_PROVENANCE.contentSha256
|
||||
) {
|
||||
errors.push(
|
||||
"upload-e2e-artifacts content must match the action reviewed at its immutable commit pin",
|
||||
);
|
||||
}
|
||||
if (!isDeepStrictEqual(sortedKeys(action), ["description", "inputs", "name", "runs"])) {
|
||||
errors.push("upload-e2e-artifacts action must expose only its canonical top-level schema");
|
||||
}
|
||||
if (
|
||||
action.name !== "upload-e2e-artifacts" ||
|
||||
action.description !== "Upload the artifacts produced by an E2E target."
|
||||
) {
|
||||
errors.push("upload-e2e-artifacts action identity must remain canonical");
|
||||
}
|
||||
if (!isDeepStrictEqual(record(action.inputs), EXPECTED_ACTION_INPUTS)) {
|
||||
errors.push("upload-e2e-artifacts action must expose only optional name and path inputs");
|
||||
}
|
||||
|
||||
const runs = record(action.runs);
|
||||
if (runs.using !== "composite" || !isDeepStrictEqual(sortedKeys(runs), ["steps", "using"])) {
|
||||
errors.push("upload-e2e-artifacts must remain a composite action with canonical run keys");
|
||||
}
|
||||
const actionSteps = steps(runs.steps);
|
||||
if (actionSteps.length !== 1) {
|
||||
errors.push("upload-e2e-artifacts must contain exactly one inner upload step");
|
||||
return errors;
|
||||
}
|
||||
|
||||
const upload = actionSteps[0];
|
||||
if (!isDeepStrictEqual(sortedKeys(upload), ["if", "name", "uses", "with"])) {
|
||||
errors.push("upload-e2e-artifacts inner step must not override its canonical contract");
|
||||
}
|
||||
if (upload.name !== "Upload E2E artifacts") {
|
||||
errors.push("upload-e2e-artifacts inner step name must remain canonical");
|
||||
}
|
||||
if (upload.if !== INNER_ALWAYS) {
|
||||
errors.push("upload-e2e-artifacts inner step must run with always()");
|
||||
}
|
||||
if (upload.uses !== UPLOAD_ARTIFACT_ACTION) {
|
||||
errors.push("upload-e2e-artifacts inner step must use the reviewed upload-artifact pin");
|
||||
}
|
||||
if (!isDeepStrictEqual(record(upload.with), EXPECTED_UPLOAD_POLICY)) {
|
||||
errors.push(
|
||||
"upload-e2e-artifacts must preserve artifact defaults, hidden-file policy, missing-file behavior, and retention",
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function validateUploadE2eArtifactsInvocations(workflow: WorkflowRecord): string[] {
|
||||
const errors: string[] = [];
|
||||
const jobs = record(workflow.jobs);
|
||||
const expectedJobs = new Set(
|
||||
Object.entries(jobs)
|
||||
.filter(([jobName, value]) => {
|
||||
const job = record(value);
|
||||
return jobName === "live" || record(job.env).E2E_JOB === "1";
|
||||
})
|
||||
.map(([jobName]) => jobName),
|
||||
);
|
||||
const defaultJobs = [...expectedJobs].filter(
|
||||
(jobName) => !EXPLICIT_UPLOAD_CONTRACTS.has(jobName),
|
||||
);
|
||||
|
||||
if (expectedJobs.size !== EXPECTED_UPLOAD_JOB_COUNT) {
|
||||
errors.push(
|
||||
`upload-e2e-artifacts must cover exactly ${EXPECTED_UPLOAD_JOB_COUNT} live and E2E_JOB execution jobs`,
|
||||
);
|
||||
}
|
||||
if (defaultJobs.length !== EXPECTED_DEFAULT_CALLER_COUNT) {
|
||||
errors.push(
|
||||
`upload-e2e-artifacts must keep exactly ${EXPECTED_DEFAULT_CALLER_COUNT} default callers`,
|
||||
);
|
||||
}
|
||||
for (const jobName of EXPLICIT_UPLOAD_CONTRACTS.keys()) {
|
||||
if (!expectedJobs.has(jobName)) {
|
||||
errors.push(`upload-e2e-artifacts explicit caller is missing: ${jobName}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [jobName, value] of Object.entries(jobs)) {
|
||||
const job = record(value);
|
||||
const jobSteps = steps(job.steps);
|
||||
const expected = expectedJobs.has(jobName);
|
||||
|
||||
for (const step of jobSteps) {
|
||||
const uses = typeof step.uses === "string" ? step.uses : "";
|
||||
if (uses.startsWith(CHECKOUT_LOCAL_UPLOAD_E2E_ARTIFACTS_ACTION)) {
|
||||
errors.push(`${jobName} must not load upload-e2e-artifacts from the target checkout`);
|
||||
}
|
||||
if (uses.startsWith(UPLOAD_ARTIFACT_ACTION_PREFIX)) {
|
||||
errors.push(`${jobName} must not invoke actions/upload-artifact directly`);
|
||||
}
|
||||
if (
|
||||
uses.startsWith(UPLOAD_E2E_ARTIFACTS_ACTION_PREFIX) &&
|
||||
uses !== UPLOAD_E2E_ARTIFACTS_ACTION
|
||||
) {
|
||||
errors.push(`${jobName} must use the reviewed immutable upload-e2e-artifacts reference`);
|
||||
}
|
||||
}
|
||||
|
||||
const uploadSteps = jobSteps.filter((step) => step.uses === UPLOAD_E2E_ARTIFACTS_ACTION);
|
||||
if (!expected) {
|
||||
if (uploadSteps.length > 0) {
|
||||
errors.push(`${jobName} must not use upload-e2e-artifacts`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (uploadSteps.length !== 1) {
|
||||
errors.push(`${jobName} must use upload-e2e-artifacts exactly once`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const upload = uploadSteps[0];
|
||||
const explicitContract = EXPLICIT_UPLOAD_CONTRACTS.get(jobName);
|
||||
const allowedKeys = explicitContract ? ["if", "name", "uses", "with"] : ["if", "name", "uses"];
|
||||
if (!isDeepStrictEqual(sortedKeys(upload), allowedKeys)) {
|
||||
errors.push(`${jobName} upload-e2e-artifacts invocation must not override its contract`);
|
||||
}
|
||||
if (typeof upload.name !== "string" || upload.name.length === 0) {
|
||||
errors.push(`${jobName} upload-e2e-artifacts invocation must retain a step name`);
|
||||
}
|
||||
if (upload.if !== CALLER_ALWAYS) {
|
||||
errors.push(`${jobName} upload-e2e-artifacts invocation must run with always()`);
|
||||
}
|
||||
const stepsAfterUpload = jobSteps.slice(jobSteps.indexOf(upload) + 1);
|
||||
if (
|
||||
stepsAfterUpload.length > 1 ||
|
||||
stepsAfterUpload.some((step) => step.name !== "Clean up Docker auth")
|
||||
) {
|
||||
errors.push(
|
||||
`${jobName} upload-e2e-artifacts invocation must follow artifact producers and precede only Docker auth cleanup`,
|
||||
);
|
||||
}
|
||||
|
||||
if (explicitContract) {
|
||||
if (!isDeepStrictEqual(record(upload.with), explicitContract)) {
|
||||
errors.push(
|
||||
`${jobName} upload-e2e-artifacts must preserve its explicit name/path contract`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.hasOwn(upload, "with")) {
|
||||
errors.push(`${jobName} upload-e2e-artifacts must use the action defaults`);
|
||||
}
|
||||
const targetId = record(job.env).E2E_TARGET_ID;
|
||||
if (typeof targetId !== "string" || !TARGET_ID_PATTERN.test(targetId)) {
|
||||
errors.push(`${jobName} default upload caller must declare a valid E2E_TARGET_ID`);
|
||||
} else if (targetId !== jobName) {
|
||||
errors.push(`${jobName} default upload caller E2E_TARGET_ID must match its job id`);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function validateUploadE2eArtifactsWorkflowBoundary(
|
||||
workflow: WorkflowRecord,
|
||||
actionPath = DEFAULT_ACTION_PATH,
|
||||
): string[] {
|
||||
return [
|
||||
...validateUploadE2eArtifactsAction(actionPath),
|
||||
...validateUploadE2eArtifactsInvocations(workflow),
|
||||
];
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import { validateE2eOperationsWorkflowBoundary } from "./operations-workflow-bou
|
|||
import { validatePrepareE2eWorkflowBoundary } from "./prepare-e2e-workflow-boundary.mts";
|
||||
import { validateSandboxOperationsWorkflow } from "./sandbox-operations-workflow-boundary.mts";
|
||||
import { validateSecurityPostureWorkflowBoundary } from "./security-posture-workflow-boundary.mts";
|
||||
import { validateUploadE2eArtifactsWorkflowBoundary } from "./upload-e2e-artifacts-workflow-boundary.mts";
|
||||
|
||||
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const DEFAULT_E2E_WORKFLOW_PATH = join(REPO_ROOT, ".github", "workflows", "e2e.yaml");
|
||||
|
|
@ -413,22 +414,6 @@ function validateInlineHostDependencyInstall(
|
|||
}
|
||||
}
|
||||
|
||||
function requireUploadPathContains(errors: string[], uploadPath: string, expected: string): void {
|
||||
if (!uploadPath.includes(expected)) {
|
||||
errors.push(`artifact upload path must include ${expected}`);
|
||||
}
|
||||
}
|
||||
|
||||
function requireUploadPathDoesNotContain(
|
||||
errors: string[],
|
||||
uploadPath: string,
|
||||
forbidden: string,
|
||||
): void {
|
||||
if (uploadPath.includes(forbidden)) {
|
||||
errors.push(`artifact upload path must not include ${forbidden}`);
|
||||
}
|
||||
}
|
||||
|
||||
function requireEnvDoesNotExposeSecret(
|
||||
errors: string[],
|
||||
owner: string,
|
||||
|
|
@ -618,9 +603,6 @@ function validateFreeStandingInventoryBoundary(
|
|||
for (const step of steps) {
|
||||
if (step.uses) {
|
||||
requireFullShaAction(errors, step, `${jobName} step '${step.name ?? step.uses}'`);
|
||||
if (stringValue(step.uses).startsWith("actions/upload-artifact@")) {
|
||||
requireUploadPathDoesNotContain(errors, stringValue(asRecord(step.with).path), "/tmp/");
|
||||
}
|
||||
}
|
||||
if (/\$\{\{\s*secrets\./.test(stringValue(step.run))) {
|
||||
errors.push(
|
||||
|
|
@ -716,24 +698,6 @@ function validateOpenShellVersionPinJob(errors: string[], jobs: WorkflowRecord):
|
|||
const runVitest = requireJobStep(errors, jobName, steps, "Run OpenShell version-pin live test");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/openshell-version-pin.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload OpenShell version-pin artifacts");
|
||||
requireFullShaAction(errors, upload, "openshell-version-pin upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-openshell-version-pin") {
|
||||
errors.push("openshell-version-pin artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/openshell-version-pin/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("openshell-version-pin artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("openshell-version-pin artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("openshell-version-pin artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateSkillAgentJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -798,42 +762,6 @@ function validateSkillAgentJob(errors: string[], jobs: WorkflowRecord): void {
|
|||
requireRunContains(errors, runVitest, "export OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/skill-agent.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload skill-agent artifacts");
|
||||
requireFullShaAction(errors, upload, "skill-agent upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-skill-agent") {
|
||||
errors.push("skill-agent artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
for (const expected of [
|
||||
"e2e-artifacts/live/skill-agent/*/artifact-summary.json",
|
||||
"e2e-artifacts/live/skill-agent/*/cleanup.json",
|
||||
"e2e-artifacts/live/skill-agent/*/cleanup-skill-agent-summary.json",
|
||||
"e2e-artifacts/live/skill-agent/*/target.json",
|
||||
"e2e-artifacts/live/skill-agent/*/target-result.json",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.result.json",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.stdout.txt",
|
||||
"e2e-artifacts/live/skill-agent/*/shell/*.stderr.txt",
|
||||
]) {
|
||||
requireUploadPathContains(errors, uploadPath, expected);
|
||||
}
|
||||
for (const line of uploadPath.split("\n")) {
|
||||
if (line.trim() === "e2e-artifacts/live/skill-agent/") {
|
||||
errors.push(
|
||||
"skill-agent artifact upload path must not list the whole skill-agent artifact directory",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("skill-agent artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("skill-agent artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("skill-agent artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateNetworkPolicyJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -940,24 +868,6 @@ function validateNetworkPolicyJob(errors: string[], jobs: WorkflowRecord): void
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/network-policy.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload network-policy artifacts");
|
||||
requireFullShaAction(errors, upload, "network-policy upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-network-policy") {
|
||||
errors.push("network-policy artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/network-policy/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("network-policy artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("network-policy artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("network-policy artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateIssue4434HostDependencies(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1077,24 +987,6 @@ function validateCommonEgressAgentJob(errors: string[], jobs: WorkflowRecord): v
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/common-egress-agent.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload common-egress agent artifacts");
|
||||
requireFullShaAction(errors, upload, "common-egress-agent upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-common-egress-agent") {
|
||||
errors.push("common-egress-agent artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/common-egress-agent/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("common-egress-agent artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("common-egress-agent artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("common-egress-agent artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateShieldsConfigJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1178,25 +1070,6 @@ function validateShieldsConfigJob(errors: string[], jobs: WorkflowRecord): void
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/shields-config.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload shields-config artifacts");
|
||||
requireFullShaAction(errors, upload, "shields-config upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-shields-config") {
|
||||
errors.push("shields-config artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/shields-config/");
|
||||
requireUploadPathDoesNotContain(errors, uploadPath, "/tmp/nemoclaw-e2e-shields-install.log");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("shields-config artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("shields-config artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("shields-config artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateRebuildOpenClawJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1270,24 +1143,6 @@ function validateRebuildOpenClawJob(errors: string[], jobs: WorkflowRecord): voi
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/rebuild-openclaw.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload OpenClaw rebuild artifacts");
|
||||
requireFullShaAction(errors, upload, "rebuild-openclaw upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-rebuild-openclaw") {
|
||||
errors.push("rebuild-openclaw artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/rebuild-openclaw/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("rebuild-openclaw artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("rebuild-openclaw artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("rebuild-openclaw artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateRebuildHermesJob(
|
||||
|
|
@ -1392,38 +1247,6 @@ function validateRebuildHermesJob(
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/rebuild-hermes.test.ts");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
options.staleBase
|
||||
? "Upload Hermes stale-base rebuild artifacts"
|
||||
: "Upload Hermes rebuild artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, `${jobName} upload-artifact`);
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
const artifactName = options.staleBase ? "e2e-rebuild-hermes-stale-base" : "e2e-rebuild-hermes";
|
||||
if (uploadWith.name !== artifactName) {
|
||||
errors.push(`${jobName} artifact upload name must be stable`);
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
options.staleBase
|
||||
? "e2e-artifacts/live/rebuild-hermes-stale-base/"
|
||||
: "e2e-artifacts/live/rebuild-hermes/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(`${jobName} artifact upload must set include-hidden-files: false`);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(`${jobName} artifact upload must ignore missing fixture artifacts`);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push(`${jobName} artifact upload retention-days must be 14`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateSandboxRebuildJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1505,24 +1328,6 @@ function validateSandboxRebuildJob(errors: string[], jobs: WorkflowRecord): void
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/sandbox-rebuild.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload sandbox rebuild artifacts");
|
||||
requireFullShaAction(errors, upload, "sandbox-rebuild upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-sandbox-rebuild") {
|
||||
errors.push("sandbox-rebuild artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/sandbox-rebuild/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("sandbox-rebuild artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("sandbox-rebuild artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("sandbox-rebuild artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateStateBackupRestoreJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1615,24 +1420,6 @@ function validateStateBackupRestoreJob(errors: string[], jobs: WorkflowRecord):
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/state-backup-restore.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload state backup restore artifacts");
|
||||
requireFullShaAction(errors, upload, "state-backup-restore upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-state-backup-restore") {
|
||||
errors.push("state-backup-restore artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/state-backup-restore/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("state-backup-restore artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("state-backup-restore artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("state-backup-restore artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateUpgradeStaleSandboxJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1724,24 +1511,6 @@ function validateUpgradeStaleSandboxJob(errors: string[], jobs: WorkflowRecord):
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/upgrade-stale-sandbox.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload upgrade stale sandbox artifacts");
|
||||
requireFullShaAction(errors, upload, "upgrade-stale-sandbox upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-upgrade-stale-sandbox") {
|
||||
errors.push("upgrade-stale-sandbox artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/upgrade-stale-sandbox/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("upgrade-stale-sandbox artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("upgrade-stale-sandbox artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("upgrade-stale-sandbox artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateTokenRotationJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1823,24 +1592,6 @@ function validateTokenRotationJob(errors: string[], jobs: WorkflowRecord): void
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/token-rotation.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload token rotation artifacts");
|
||||
requireFullShaAction(errors, upload, "token-rotation upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-token-rotation") {
|
||||
errors.push("token-rotation artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/token-rotation/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("token-rotation artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("token-rotation artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("token-rotation artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateMessagingCompatibleEndpointJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -1962,37 +1713,6 @@ function validateMessagingCompatibleEndpointJob(errors: string[], jobs: Workflow
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/messaging-compatible-endpoint.test.ts");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload messaging compatible endpoint artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "messaging-compatible-endpoint upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-messaging-compatible-endpoint") {
|
||||
errors.push("messaging-compatible-endpoint artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/messaging-compatible-endpoint/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"messaging-compatible-endpoint artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"messaging-compatible-endpoint artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("messaging-compatible-endpoint artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateOnboardNegativePathsJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2047,24 +1767,6 @@ function validateOnboardNegativePathsJob(errors: string[], jobs: WorkflowRecord)
|
|||
const runVitest = requireJobStep(errors, jobName, steps, "Run onboard negative-paths live test");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/onboard-negative-paths.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload onboard negative-paths artifacts");
|
||||
requireFullShaAction(errors, upload, "onboard-negative-paths upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-onboard-negative-paths") {
|
||||
errors.push("onboard-negative-paths artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/onboard-negative-paths/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("onboard-negative-paths artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("onboard-negative-paths artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("onboard-negative-paths artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateCloudInferenceJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2130,24 +1832,6 @@ function validateCloudInferenceJob(errors: string[], jobs: WorkflowRecord): void
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/cloud-inference.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload cloud inference artifacts");
|
||||
requireFullShaAction(errors, upload, "cloud-inference upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-cloud-inference") {
|
||||
errors.push("cloud-inference artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/cloud-inference/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("cloud-inference artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("cloud-inference artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("cloud-inference artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function requireNoDockerHubAuthInRun(errors: string[], owner: string, runScript: string): void {
|
||||
|
|
@ -2474,24 +2158,6 @@ function validateDoubleOnboardJob(errors: string[], jobs: WorkflowRecord): void
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/double-onboard.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload double-onboard Vitest artifacts");
|
||||
requireFullShaAction(errors, upload, "double-onboard upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-double-onboard") {
|
||||
errors.push("double-onboard artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/double-onboard/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("double-onboard artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("double-onboard artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("double-onboard artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
function validateRuntimeOverridesJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
const jobName = "runtime-overrides";
|
||||
|
|
@ -2547,24 +2213,6 @@ function validateRuntimeOverridesJob(errors: string[], jobs: WorkflowRecord): vo
|
|||
const runVitest = requireJobStep(errors, jobName, steps, "Run runtime overrides live test");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/runtime-overrides.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload runtime overrides artifacts");
|
||||
requireFullShaAction(errors, upload, "runtime-overrides upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-runtime-overrides") {
|
||||
errors.push("runtime-overrides artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/runtime-overrides/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("runtime-overrides artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("runtime-overrides artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("runtime-overrides artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateHermesE2EJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2637,24 +2285,6 @@ function validateHermesE2EJob(errors: string[], jobs: WorkflowRecord): void {
|
|||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/hermes-e2e.test.ts");
|
||||
requireRunDoesNotContain(errors, runVitest, "${{ inputs.");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload Hermes live Vitest artifacts");
|
||||
requireFullShaAction(errors, upload, "hermes-e2e upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-hermes-e2e") {
|
||||
errors.push("hermes-e2e artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/hermes-e2e/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("hermes-e2e artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("hermes-e2e artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("hermes-e2e artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateHermesRootEntrypointSmokeJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2761,33 +2391,6 @@ function validateHermesRootEntrypointSmokeJob(errors: string[], jobs: WorkflowRe
|
|||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/hermes-root-entrypoint-smoke.test.ts");
|
||||
requireRunDoesNotContain(errors, runVitest, "${{ inputs.");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload Hermes root entrypoint smoke artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "hermes-root-entrypoint-smoke upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-hermes-root-entrypoint-smoke") {
|
||||
errors.push("hermes-root-entrypoint-smoke artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/hermes-root-entrypoint-smoke/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"hermes-root-entrypoint-smoke artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"hermes-root-entrypoint-smoke artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("hermes-root-entrypoint-smoke artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateHermesSandboxSecretBoundaryJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2854,37 +2457,6 @@ function validateHermesSandboxSecretBoundaryJob(errors: string[], jobs: Workflow
|
|||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/hermes-sandbox-secret-boundary.test.ts");
|
||||
requireRunDoesNotContain(errors, runVitest, "${{ inputs.");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload Hermes sandbox secret-boundary artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "hermes-sandbox-secret-boundary upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-hermes-sandbox-secret-boundary") {
|
||||
errors.push("hermes-sandbox-secret-boundary artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/hermes-sandbox-secret-boundary/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"hermes-sandbox-secret-boundary artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"hermes-sandbox-secret-boundary artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("hermes-sandbox-secret-boundary artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateDiagnosticsJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -2969,24 +2541,6 @@ function validateDiagnosticsJob(errors: string[], jobs: WorkflowRecord): void {
|
|||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/diagnostics.test.ts");
|
||||
requireRunDoesNotContain(errors, runVitest, "${{ inputs.");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload diagnostics artifacts");
|
||||
requireFullShaAction(errors, upload, "diagnostics upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-diagnostics") {
|
||||
errors.push("diagnostics artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/diagnostics/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("diagnostics artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("diagnostics artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("diagnostics artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateSparkInstallJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3070,24 +2624,6 @@ function validateSparkInstallJob(errors: string[], jobs: WorkflowRecord): void {
|
|||
requireRunContains(errors, runVitest, "set -euo pipefail");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/spark-install.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload Spark install artifacts");
|
||||
requireFullShaAction(errors, upload, "spark-install upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-spark-install") {
|
||||
errors.push("spark-install artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/spark-install/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("spark-install artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("spark-install artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("spark-install artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateSnapshotCommandsJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3174,24 +2710,6 @@ function validateSnapshotCommandsJob(errors: string[], jobs: WorkflowRecord): vo
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/snapshot-commands.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload snapshot commands artifacts");
|
||||
requireFullShaAction(errors, upload, "snapshot-commands upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-snapshot-commands") {
|
||||
errors.push("snapshot-commands artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/snapshot-commands/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("snapshot-commands artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("snapshot-commands artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("snapshot-commands artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateModelRouterProviderRoutedInferenceJob(
|
||||
|
|
@ -3297,37 +2815,6 @@ function validateModelRouterProviderRoutedInferenceJob(
|
|||
runVitest,
|
||||
"test/e2e/live/model-router-provider-routed-inference.test.ts",
|
||||
);
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload Model Router provider-routed inference artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "model-router-provider-routed-inference upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-model-router-provider-routed-inference") {
|
||||
errors.push("model-router-provider-routed-inference artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/model-router-provider-routed-inference/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"model-router-provider-routed-inference artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"model-router-provider-routed-inference artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("model-router-provider-routed-inference artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateGatewayDriftPreflightJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3477,24 +2964,6 @@ function validateTunnelLifecycleJob(errors: string[], jobs: WorkflowRecord): voi
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/tunnel-lifecycle.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload tunnel lifecycle artifacts");
|
||||
requireFullShaAction(errors, upload, "tunnel-lifecycle upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-tunnel-lifecycle") {
|
||||
errors.push("tunnel-lifecycle artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/tunnel-lifecycle/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("tunnel-lifecycle artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("tunnel-lifecycle artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("tunnel-lifecycle artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateIssue2478CrashLoopRecoveryJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3580,37 +3049,6 @@ function validateIssue2478CrashLoopRecoveryJob(errors: string[], jobs: WorkflowR
|
|||
);
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/issue-2478-crash-loop-recovery.test.ts");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload issue #2478 crash-loop recovery artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "issue-2478-crash-loop-recovery upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-issue-2478-crash-loop-recovery") {
|
||||
errors.push("issue-2478-crash-loop-recovery artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/issue-2478-crash-loop-recovery/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"issue-2478-crash-loop-recovery artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"issue-2478-crash-loop-recovery artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("issue-2478-crash-loop-recovery artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateChannelsAddRemoveJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3737,24 +3175,6 @@ function validateChannelsAddRemoveJob(errors: string[], jobs: WorkflowRecord): v
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/channels-add-remove.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload channels add/remove artifacts");
|
||||
requireFullShaAction(errors, upload, "channels-add-remove upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-channels-add-remove") {
|
||||
errors.push("channels-add-remove artifact upload name must be stable");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/channels-add-remove/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("channels-add-remove artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("channels-add-remove artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("channels-add-remove artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateOpenClawDiscordPairingJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3828,26 +3248,6 @@ function validateOpenClawDiscordPairingJob(errors: string[], jobs: WorkflowRecor
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/openclaw-discord-pairing.test.ts");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload OpenClaw Discord pairing artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "openclaw-discord-pairing upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/openclaw-discord-pairing/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("openclaw-discord-pairing artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("openclaw-discord-pairing artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("openclaw-discord-pairing artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateOpenClawSlackPairingJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -3919,21 +3319,6 @@ function validateOpenClawSlackPairingJob(errors: string[], jobs: WorkflowRecord)
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/openclaw-slack-pairing.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload OpenClaw Slack pairing artifacts");
|
||||
requireFullShaAction(errors, upload, "openclaw-slack-pairing upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/openclaw-slack-pairing/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("openclaw-slack-pairing artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("openclaw-slack-pairing artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("openclaw-slack-pairing artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateChannelsStopStartJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -4063,28 +3448,6 @@ function validateChannelsStopStartJob(errors: string[], jobs: WorkflowRecord): v
|
|||
requireRunContains(errors, runVitest, "OPENSHELL_BIN");
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/channels-stop-start.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload channels stop/start artifacts");
|
||||
requireFullShaAction(errors, upload, "channels-stop-start upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-channels-stop-start-${{ matrix.agent }}") {
|
||||
errors.push("channels-stop-start artifact upload name must include matrix.agent");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/channels-stop-start/${{ matrix.agent }}/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("channels-stop-start artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("channels-stop-start artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("channels-stop-start artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateTelegramInjectionJob(errors: string[], jobs: WorkflowRecord): void {
|
||||
|
|
@ -4144,20 +3507,6 @@ function validateTelegramInjectionJob(errors: string[], jobs: WorkflowRecord): v
|
|||
}
|
||||
requireRunContains(errors, runVitest, "npx vitest run --project e2e-live");
|
||||
requireRunContains(errors, runVitest, "test/e2e/live/telegram-injection.test.ts");
|
||||
|
||||
const upload = requireJobStep(errors, jobName, steps, "Upload Telegram injection artifacts");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/telegram-injection/");
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("telegram-injection artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("telegram-injection artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("telegram-injection artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
function validateBedrockRuntimeCompatibleAnthropicJob(
|
||||
|
|
@ -4285,45 +3634,13 @@ function validateBedrockRuntimeCompatibleAnthropicJob(
|
|||
"test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts",
|
||||
);
|
||||
requireRunDoesNotContain(errors, runVitest, "${{ inputs.");
|
||||
|
||||
const upload = requireJobStep(
|
||||
errors,
|
||||
jobName,
|
||||
steps,
|
||||
"Upload Bedrock Runtime compatible Anthropic artifacts",
|
||||
);
|
||||
requireFullShaAction(errors, upload, "bedrock-runtime-compatible-anthropic upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}") {
|
||||
errors.push(
|
||||
"bedrock-runtime-compatible-anthropic artifact upload name must include matrix.agent",
|
||||
);
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }}/",
|
||||
);
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push(
|
||||
"bedrock-runtime-compatible-anthropic artifact upload must set include-hidden-files: false",
|
||||
);
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push(
|
||||
"bedrock-runtime-compatible-anthropic artifact upload must ignore missing fixture artifacts",
|
||||
);
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("bedrock-runtime-compatible-anthropic artifact upload retention-days must be 14");
|
||||
}
|
||||
}
|
||||
|
||||
export function validateE2eWorkflowBoundary(workflowPath = DEFAULT_E2E_WORKFLOW_PATH): string[] {
|
||||
const workflow = readWorkflowRecord(workflowPath);
|
||||
const errors: string[] = [];
|
||||
errors.push(...validatePrepareE2eWorkflowBoundary(workflow));
|
||||
errors.push(...validateUploadE2eArtifactsWorkflowBoundary(workflow));
|
||||
errors.push(...validateHermesDashboardWorkflowBoundary(workflowPath));
|
||||
errors.push(...validateInferenceSwitchWorkflowBoundary(workflowPath));
|
||||
errors.push(...validateE2eOperationsWorkflowBoundary(workflowPath));
|
||||
|
|
@ -4518,57 +3835,6 @@ export function validateE2eWorkflowBoundary(workflowPath = DEFAULT_E2E_WORKFLOW_
|
|||
requireRunContains(errors, summary, "| Target | Manifest | Expected state | Suites | Phases |");
|
||||
requireRunContains(errors, summary, "TARGET_ID");
|
||||
|
||||
const upload = requireStep(errors, steps, "Upload E2E artifacts");
|
||||
requireFullShaAction(errors, upload, "upload-artifact");
|
||||
const uploadWith = asRecord(upload?.with);
|
||||
if (uploadWith.name !== "e2e-${{ matrix.id }}") {
|
||||
errors.push("artifact upload name must include matrix.id");
|
||||
}
|
||||
const uploadPath = stringValue(uploadWith.path);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/${{ matrix.id }}/run-plan.json",
|
||||
);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/${{ matrix.id }}/target.json");
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/${{ matrix.id }}/target-result.json",
|
||||
);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/${{ matrix.id }}/environment.result.json",
|
||||
);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/${{ matrix.id }}/onboarding.result.json",
|
||||
);
|
||||
requireUploadPathContains(
|
||||
errors,
|
||||
uploadPath,
|
||||
"e2e-artifacts/live/${{ matrix.id }}/state-validation.result.json",
|
||||
);
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/${{ matrix.id }}/actions/");
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/${{ matrix.id }}/logs/");
|
||||
requireUploadPathContains(errors, uploadPath, "e2e-artifacts/live/${{ matrix.id }}/shell/");
|
||||
for (const line of uploadPath.split("\n")) {
|
||||
if (line.trim() === "e2e-artifacts/live/${{ matrix.id }}/") {
|
||||
errors.push("artifact upload path must not list the whole matrix artifact directory");
|
||||
}
|
||||
}
|
||||
if (uploadWith["include-hidden-files"] !== false) {
|
||||
errors.push("artifact upload must set include-hidden-files: false");
|
||||
}
|
||||
if (uploadWith["if-no-files-found"] !== "ignore") {
|
||||
errors.push("artifact upload must ignore missing fixture artifacts");
|
||||
}
|
||||
if (uploadWith["retention-days"] !== 14) {
|
||||
errors.push("artifact upload retention-days must be 14");
|
||||
}
|
||||
|
||||
validateOpenShellVersionPinJob(errors, jobs);
|
||||
validateOnboardNegativePathsJob(errors, jobs);
|
||||
validateSkillAgentJob(errors, jobs);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue