Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,11 @@ jobs:
run: node --experimental-strip-types --no-warnings tools/e2e/dcode-base-image-contract.mts "${RUNNER_TEMP}/dcode-base-contract/contract.json"

generate-matrix:
needs: base-image-publication
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
cli_artifact_provenance: ${{ steps.record_cli_artifact.outputs.provenance }}
e2e_credentials_allowed: ${{ steps.e2e_credentials.outputs.allowed }}
dcode_base_contract: ${{ needs.base-image-publication.outputs.dcode_base_contract }}
dcode_base_ref: ${{ needs.base-image-publication.outputs.dcode_base_ref }}
matrix: ${{ steps.matrix.outputs.matrix }}
test_matrix: ${{ steps.matrix.outputs.test_matrix }}
hermes_selected: ${{ steps.matrix.outputs.hermes_selected }}
Expand Down Expand Up @@ -907,7 +904,7 @@ jobs:
${{ steps.workspace.outputs.work_dir }}/cleanup.json

live:
needs: generate-matrix
needs: [base-image-publication, generate-matrix]
if: ${{ needs.generate-matrix.outputs.matrix != '[]' }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
Expand All @@ -917,7 +914,7 @@ jobs:
include: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live
NEMOCLAW_LANGCHAIN_DEEPAGENTS_CODE_SANDBOX_BASE_IMAGE_REF: ${{ needs.generate-matrix.outputs.dcode_base_ref }}
NEMOCLAW_LANGCHAIN_DEEPAGENTS_CODE_SANDBOX_BASE_IMAGE_REF: ${{ needs.base-image-publication.outputs.dcode_base_ref }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
Expand Down Expand Up @@ -974,7 +971,7 @@ jobs:
- name: Record immutable Deep Agents Code base evidence
if: ${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }}
env:
BASE_CONTRACT: ${{ needs.generate-matrix.outputs.dcode_base_contract }}
BASE_CONTRACT: ${{ needs.base-image-publication.outputs.dcode_base_contract }}
CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }}
TARGET_ID: ${{ matrix.id }}
shell: bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ function runClassifier(environment: {
}

describe("base-image publication workflow boundary (#7372)", () => {
it("keeps Launchable off the base-image publication critical path", () => {
const value = workflow();

expect(validate(value)).toEqual([]);
});

// source-shape-contract: security -- Immutable base contracts must outlive the qualification interval so later E2E cannot fall back to a mutable alias.
it("retains immutable base contracts for later qualification (#9049)", () => {
const action = YAML.parse(
Expand Down Expand Up @@ -209,7 +215,19 @@ describe("base-image publication workflow boundary (#7372)", () => {
(gateSteps(value)[5].run = "node tools/e2e/dcode-base-image-contract.mts contract.json"),
],
["step count", (value) => gateSteps(value).push({ name: "Unreviewed step", run: "true" })],
["fanout dependency", (value) => (value.jobs["generate-matrix"].needs = [])],
[
"matrix publication dependency",
(value) => (value.jobs["generate-matrix"].needs = "base-image-publication"),
],
["live publication dependency", (value) => (value.jobs.live.needs = ["generate-matrix"])],
[
"Launchable publication dependency",
(value) =>
(value.jobs["staging-brev-launchable"].needs = [
"base-image-publication",
"generate-matrix",
]),
],
[
"matrix base output",
(value) => {
Expand Down
10 changes: 6 additions & 4 deletions tools/e2e/cli-artifact-workflow-boundary.mts
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,12 @@ function validateConsumer(
job: WorkflowRecord,
jobSteps: WorkflowStep[],
): void {
const expectedNeeds =
jobName === "mcp-bridge-dev"
? [CLI_ARTIFACT_PRODUCER_JOB, "openshell-dev-artifact"]
: CLI_ARTIFACT_PRODUCER_JOB;
let expectedNeeds: string | string[] = CLI_ARTIFACT_PRODUCER_JOB;
if (jobName === "mcp-bridge-dev") {
expectedNeeds = [CLI_ARTIFACT_PRODUCER_JOB, "openshell-dev-artifact"];
} else if (jobName === "live") {
expectedNeeds = ["base-image-publication", CLI_ARTIFACT_PRODUCER_JOB];
}
if (!isDeepStrictEqual(job.needs, expectedNeeds)) {
errors.push(`${jobName} must depend directly on the CLI artifact producer`);
}
Expand Down
26 changes: 15 additions & 11 deletions tools/e2e/operations-workflow-boundary.mts
Original file line number Diff line number Diff line change
Expand Up @@ -578,21 +578,21 @@ export function validateBaseImagePublicationGate(workflow: OperationsWorkflow):
"base-image-publication job must preserve its exact trusted-mode classifier, minimal permissions, pinned checkout, and verifier boundary",
);
}
if (!needs(workflow.jobs["generate-matrix"] ?? {}).includes("base-image-publication")) {
errors.push("generate-matrix must wait for base-image-publication");
const matrix = workflow.jobs["generate-matrix"] ?? {};
if (needs(matrix).includes("base-image-publication")) {
errors.push("generate-matrix must not wait for base-image-publication");
}
const matrixOutputs = workflow.jobs["generate-matrix"]?.outputs ?? {};
if (
matrixOutputs.dcode_base_contract !==
"${{ needs.base-image-publication.outputs.dcode_base_contract }}" ||
matrixOutputs.dcode_base_ref !== "${{ needs.base-image-publication.outputs.dcode_base_ref }}"
) {
errors.push("generate-matrix must preserve the immutable Deep Agents Code base outputs");
const matrixOutputs = matrix.outputs ?? {};
if ("dcode_base_contract" in matrixOutputs || "dcode_base_ref" in matrixOutputs) {
errors.push("generate-matrix must not relay Deep Agents Code base outputs");
}
const live = workflow.jobs.live ?? {};
if (!sameMembers(needs(live), ["base-image-publication", "generate-matrix"])) {
errors.push("live E2E must wait for matrix generation and base-image publication");
}
if (
live.env?.NEMOCLAW_LANGCHAIN_DEEPAGENTS_CODE_SANDBOX_BASE_IMAGE_REF !==
"${{ needs.generate-matrix.outputs.dcode_base_ref }}"
"${{ needs.base-image-publication.outputs.dcode_base_ref }}"
) {
errors.push("live DCode must use the selected immutable base reference");
}
Expand All @@ -601,13 +601,17 @@ export function validateBaseImagePublicationGate(workflow: OperationsWorkflow):
const liveSteps = live.steps ?? [];
if (
evidence.if !== "${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }}" ||
evidence.env?.BASE_CONTRACT !== "${{ needs.generate-matrix.outputs.dcode_base_contract }}" ||
evidence.env?.BASE_CONTRACT !==
"${{ needs.base-image-publication.outputs.dcode_base_contract }}" ||
!String(evidence.run ?? "").includes("dcode-base-image.json") ||
liveSteps.indexOf(evidence) >= liveSteps.indexOf(findStep(live, "Run live E2E tests")) ||
!String(upload.with?.path ?? "").includes("dcode-base-image.json")
) {
errors.push("live DCode must record its immutable base contract before E2E execution");
}
if (!sameMembers(needs(workflow.jobs["staging-brev-launchable"] ?? {}), ["generate-matrix"])) {
errors.push("staging-brev-launchable must wait only for generate-matrix");
}
return errors;
}

Expand Down
6 changes: 4 additions & 2 deletions tools/e2e/workflow-boundary.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2443,8 +2443,10 @@ export function validateE2eWorkflow(workflowValue: unknown): string[] {
if (liveTargets["runs-on"] !== "${{ matrix.runner }}") {
errors.push("live job must run on the matrix runner");
}
if (liveTargets.needs !== "generate-matrix") {
errors.push("live job must depend on generate-matrix");
if (
!isDeepStrictEqual(liveTargets.needs, ["base-image-publication", "generate-matrix"])
) {
errors.push("live job must depend on base-image-publication and generate-matrix");
}
if (liveTargets.if !== "${{ needs.generate-matrix.outputs.matrix != '[]' }}") {
errors.push("live job must run whenever the trusted planner emits typed targets");
Expand Down
Loading