Skip to content

fix(ql3): preserve adopted baseline during capture #157

fix(ql3): preserve adopted baseline during capture

fix(ql3): preserve adopted baseline during capture #157

Workflow file for this run

name: QingLong 3.0 CI
on:
push:
branches:
- next
pull_request:
branches:
- next
workflow_dispatch:
inputs:
produce_alpha_artifacts:
description: Package tested native images as commit-bound Alpha artifacts
required: true
default: false
type: boolean
alpha_artifact_scope:
description: Select the product family packaged by an authorized Alpha milestone
required: true
default: local
type: choice
options:
- local
- cluster
- all
local_alpha_variant:
description: Select the Local Alpha deployment surface without changing the default headless image
required: true
default: headless
type: choice
options:
- headless
- console
permissions:
contents: read
concurrency:
group: ql3-ci-${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && github.run_id || 'validation' }}
cancel-in-progress: ${{ !(github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts) }}
jobs:
backend:
name: Backend (${{ matrix.arch }}, Node ${{ matrix.node }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
node: '24.18.0'
- runner: ubuntu-24.04-arm
arch: arm64
node: '24.18.0'
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Initialize the public test environment
run: |
cp .env.example .env
mkdir -p data/db
- name: Rebuild the reviewed native SQLite binding
run: pnpm rebuild @whyour/sqlite3
- name: Build backend and QL3 workspace from source
run: |
pnpm build:back
pnpm run build:packages:ql3
- name: Test backend migrations
run: pnpm test:back
- name: Audit edge dependency and import isolation
run: |
pnpm audit:edge-imports:ql3
pnpm audit:cluster-dependencies:ql3
pnpm audit:cluster-deployment:ql3
pnpm audit:security-administration-kubernetes:ql3
- name: Smoke benchmark edge executor
run: pnpm benchmark:edge -- --json
- name: Prove disabled AI Profile has zero storage or credential reachability
run: >-
pnpm benchmark:ai-profile --
--json
--max-rss-delta-mb=8
--max-disabled-activation-ms=50
- name: Benchmark Node SQLite transaction boundary
run: >-
pnpm benchmark:db:node-sqlite --
--json
--iterations=250
--batch-size=10
--max-transaction-p95-ms=250
--max-batch-stall-ms=2500
--max-rss-delta-mb=64
- name: Enforce bounded receipt maintenance under edge budget
run: >-
docker run --rm
--memory=256m
--memory-swap=256m
--cpus=1
--pids-limit=128
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:${{ matrix.node }}-bookworm-slim
node --test test/back/completionReceiptOrphanAuditor.test.cjs
service-manager-bridge:
name: systemd/OpenRC dual-authority bridge
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build the complete local test fixture workspace
run: pnpm run build:packages:ql3
- name: Build and test Local Owner product closure
run: pnpm --filter @qinglong/local-owner-cli test
- name: Audit root bridge static import closure
run: pnpm audit:service-manager-bridge-imports:ql3
- name: Run real systemd/OpenRC root and non-root bridge gate
run: pnpm test:service-manager-bridge:ql3
linux-resource-envelopes:
name: Linux resource envelopes (${{ matrix.arch }}, Node 24)
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
- runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Initialize the public test environment
run: |
cp .env.example .env
mkdir -p data/db
- name: Rebuild the reviewed native SQLite binding
run: pnpm rebuild @whyour/sqlite3
- name: Build benchmark targets once
run: |
pnpm build:back
pnpm run build:packages:ql3
- name: Test fail-closed Linux resource gate
run: node --test test/back/ql3LinuxResourceGate.test.cjs test/back/ql3LinuxResourceWorkflow.test.cjs test/back/ql3LegacyShadowResourceRollback.test.cjs test/back/ql3PhysicalEdgeEvidence.test.cjs test/back/ql3PhysicalEdgeIdleSampler.test.cjs test/back/ql3PhysicalEdgeFaultProbe.test.cjs test/back/ql3PhysicalEdgeTaskScale.test.cjs
- name: Prepare architecture evidence directory
run: mkdir -p "${RUNNER_TEMP}/ql3-linux-resource-evidence"
- name: Stress router headroom without claiming a supported minimum
run: >-
docker run --rm --read-only
--security-opt no-new-privileges
--user 65532:65532
--memory=128m
--memory-swap=128m
--cpus=0.5
--pids-limit=64
--tmpfs /tmp:rw,nosuid,nodev,noexec,mode=1777,size=64m
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:24.18.0-bookworm-slim
node scripts/ql3-linux-resource-gate.cjs
--tier=router-stress-ci
--expected-arch=${{ matrix.arch }}
--json
> "${RUNNER_TEMP}/ql3-linux-resource-evidence/router-stress-ci.json"
- name: Enforce the emulated edge release envelope
run: >-
docker run --rm --read-only
--security-opt no-new-privileges
--user 65532:65532
--memory=256m
--memory-swap=256m
--cpus=1
--pids-limit=128
--tmpfs /tmp:rw,nosuid,nodev,noexec,mode=1777,size=64m
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:24.18.0-bookworm-slim
node scripts/ql3-linux-resource-gate.cjs
--tier=edge-release-ci
--expected-arch=${{ matrix.arch }}
--json
> "${RUNNER_TEMP}/ql3-linux-resource-evidence/edge-release-ci.json"
- name: Enforce the cluster control-plane guard independently
run: >-
docker run --rm --read-only
--security-opt no-new-privileges
--user 65532:65532
--memory=512m
--memory-swap=512m
--cpus=2
--pids-limit=256
--tmpfs /tmp:rw,nosuid,nodev,noexec,mode=1777,size=32m
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:24.18.0-bookworm-slim
node scripts/ql3-linux-resource-gate.cjs
--tier=cluster-control-ci
--expected-arch=${{ matrix.arch }}
--json
> "${RUNNER_TEMP}/ql3-linux-resource-evidence/cluster-control-ci.json"
- name: Bind native resource evidence to this workflow run
env:
SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_REVISION: ${{ github.sha }}
SOURCE_WORKFLOW: ${{ github.workflow }}
SOURCE_RUN_ID: ${{ github.run_id }}
SOURCE_RUN_ATTEMPT: ${{ github.run_attempt }}
run: >-
node scripts/ql3-linux-resource-release-evidence.cjs
--mode=bundle
--repository="${SOURCE_REPOSITORY}"
--revision="${SOURCE_REVISION}"
--workflow="${SOURCE_WORKFLOW}"
--run-id="${SOURCE_RUN_ID}"
--run-attempt="${SOURCE_RUN_ATTEMPT}"
--architecture="${{ matrix.arch }}"
--router-stress-ci="${RUNNER_TEMP}/ql3-linux-resource-evidence/router-stress-ci.json"
--edge-release-ci="${RUNNER_TEMP}/ql3-linux-resource-evidence/edge-release-ci.json"
--cluster-control-ci="${RUNNER_TEMP}/ql3-linux-resource-evidence/cluster-control-ci.json"
--output="${RUNNER_TEMP}/ql3-linux-resource-evidence/${{ matrix.arch }}.json"
- name: Upload native resource evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-linux-resource-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.arch }}
path: ${{ runner.temp }}/ql3-linux-resource-evidence/${{ matrix.arch }}.json
if-no-files-found: error
retention-days: 14
compression-level: 0
overwrite: false
include-hidden-files: false
linux-resource-release-evidence:
name: Cross-architecture Linux resource release evidence
needs: linux-resource-envelopes
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Download native x64 resource evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-linux-resource-${{ github.run_id }}-${{ github.run_attempt }}-x64
path: ${{ runner.temp }}/ql3-linux-resource-evidence/x64
- name: Download native arm64 resource evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-linux-resource-${{ github.run_id }}-${{ github.run_attempt }}-arm64
path: ${{ runner.temp }}/ql3-linux-resource-evidence/arm64
- name: Merge source-bound cross-architecture evidence
env:
SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_REVISION: ${{ github.sha }}
SOURCE_WORKFLOW: ${{ github.workflow }}
SOURCE_RUN_ID: ${{ github.run_id }}
SOURCE_RUN_ATTEMPT: ${{ github.run_attempt }}
run: >-
node scripts/ql3-linux-resource-release-evidence.cjs
--mode=merge
--repository="${SOURCE_REPOSITORY}"
--revision="${SOURCE_REVISION}"
--workflow="${SOURCE_WORKFLOW}"
--run-id="${SOURCE_RUN_ID}"
--run-attempt="${SOURCE_RUN_ATTEMPT}"
--x64="${RUNNER_TEMP}/ql3-linux-resource-evidence/x64/x64.json"
--arm64="${RUNNER_TEMP}/ql3-linux-resource-evidence/arm64/arm64.json"
--output="${RUNNER_TEMP}/ql3-linux-resource-evidence/cross-architecture.json"
- name: Upload cross-architecture resource release evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-linux-resource-release-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-linux-resource-evidence/cross-architecture.json
if-no-files-found: error
retention-days: 14
compression-level: 0
overwrite: false
include-hidden-files: false
supply-chain:
name: Profile supply-chain gate
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Test bounded fail-closed audit classifier
run: >-
node --test
test/back/ql3ProfileVulnerabilityAudit.test.cjs
test/back/ql3ClusterImageSbom.test.cjs
test/back/ql3ClusterImageReleaseAudit.test.cjs
test/back/ql3ReleaseCandidateContract.test.cjs
test/back/ql3PrivateReleaseEvidenceReceiptContract.test.cjs
test/back/ql3ReleaseSetContract.test.cjs
test/back/ql3ReleaseCatalogContract.test.cjs
test/back/ql3ReleaseDeploymentReadinessContract.test.cjs
test/back/ql3ReleaseTagFinalizer.test.cjs
test/back/ql3ReleasePublicationClosureContract.test.cjs
test/back/ql3ReleaseCatalogConsumptionCeremony.test.cjs
test/back/ql3DeploymentLockContract.test.cjs
test/back/ql3ImageOsVulnerabilityPolicy.test.cjs
test/back/ql3LocalImageAudit.test.cjs
test/back/ql3VersionTransition.test.cjs
- name: Audit cluster image release contract
run: |
pnpm audit:release-version:ql3
pnpm audit:image-release:ql3
pnpm audit:deployment-lock-surfaces:ql3
pnpm audit:local-image:ql3
- name: Reject high or critical advisories in 3.0 Profile importers
run: pnpm audit:profiles:ql3
local-image:
name: Local application image (${{ matrix.image_arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.node_arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit immutable local image inputs
run: |
node --test test/back/ql3LocalImageAudit.test.cjs
node --test test/back/ql3LocalOperatorImageAudit.test.cjs
node --test test/back/ql3LocalAlphaMilestone.test.cjs
pnpm audit:local-image:ql3
pnpm audit:local-operator-image:ql3
- name: Build the native AI-excluded image
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
run: >-
docker build
--file deploy/containers/ql3-local-application/Dockerfile
--target runtime
--build-arg SOURCE_REVISION=${{ github.sha }}
--tag "${IMAGE}"
.
- name: Build the opt-in Local Console image
env:
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
run: >-
docker build
--file deploy/containers/ql3-local-application/Dockerfile
--target runtime-console
--build-arg SOURCE_REVISION=${{ github.sha }}
--tag "${CONSOLE_IMAGE}"
.
- name: Build the native short-lived Local operator image
env:
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: >-
docker build
--file deploy/containers/ql3-local-operator/Dockerfile
--build-arg SOURCE_REVISION=${{ github.sha }}
--tag "${OPERATOR_IMAGE}"
.
- name: Materialize the reviewed Local OS vulnerability exceptions
run: |
node scripts/ql3-image-os-vulnerability-policy.cjs \
--image=local \
--output=${{ runner.temp }}/ql3-local-${{ matrix.image_arch }}.trivyignore.yaml
node scripts/ql3-image-os-vulnerability-policy.cjs \
--image=local-operator \
--output=${{ runner.temp }}/ql3-local-operator-${{ matrix.image_arch }}.trivyignore.yaml
node scripts/ql3-image-os-vulnerability-policy.cjs \
--image=local-console \
--output=${{ runner.temp }}/ql3-local-console-${{ matrix.image_arch }}.trivyignore.yaml
- name: Reject unexcepted high or critical local OS vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: 'v0.70.0'
image-ref: qinglong3-local-application:ci-${{ matrix.image_arch }}
scanners: 'vuln'
vuln-type: 'os'
severity: 'HIGH,CRITICAL'
ignore-unfixed: 'false'
exit-code: '1'
format: 'table'
hide-progress: 'true'
timeout: '10m0s'
cache: 'false'
trivyignores: ${{ runner.temp }}/ql3-local-${{ matrix.image_arch }}.trivyignore.yaml
- name: Reject high or critical Local operator OS vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: 'v0.70.0'
image-ref: qinglong3-local-operator:ci-${{ matrix.image_arch }}
scanners: 'vuln'
vuln-type: 'os'
severity: 'HIGH,CRITICAL'
ignore-unfixed: 'false'
exit-code: '1'
format: 'table'
hide-progress: 'true'
timeout: '10m0s'
cache: 'false'
trivyignores: ${{ runner.temp }}/ql3-local-operator-${{ matrix.image_arch }}.trivyignore.yaml
- name: Reject unexcepted high or critical Local Console OS vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: 'v0.70.0'
image-ref: qinglong3-local-console:ci-${{ matrix.image_arch }}
scanners: 'vuln'
vuln-type: 'os'
severity: 'HIGH,CRITICAL'
ignore-unfixed: 'false'
exit-code: '1'
format: 'table'
hide-progress: 'true'
timeout: '10m0s'
cache: 'false'
trivyignores: ${{ runner.temp }}/ql3-local-console-${{ matrix.image_arch }}.trivyignore.yaml
- name: Verify non-root identity and architecture
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} 65532:65532 2,3,4 51 52 52 1
run: |
set -euo pipefail
actual="$(docker image inspect --format '{{.Architecture}} {{.Config.User}} {{index .Config.Labels "io.qinglong.local.application-config"}} {{index .Config.Labels "io.qinglong.local.sqlite-contract-min"}} {{index .Config.Labels "io.qinglong.local.sqlite-contract-max"}} {{index .Config.Labels "io.qinglong.local.sqlite-write-contract"}} {{index .Config.Labels "io.qinglong.local.compose-selection"}}' "${IMAGE}")"
if [[ "${actual}" != "${EXPECTED}" ]]; then
echo "unexpected image contract: ${actual}" >&2
exit 1
fi
- name: Verify the short-lived Local operator identity
env:
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} 65532:65532 short-lived local-owner-management none-by-default
run: |
set -euo pipefail
actual="$(docker image inspect --format '{{.Architecture}} {{.Config.User}} {{index .Config.Labels "io.qinglong.lifecycle"}} {{index .Config.Labels "io.qinglong.authority"}} {{index .Config.Labels "io.qinglong.network"}}' "${OPERATOR_IMAGE}")"
if [[ "${actual}" != "${EXPECTED}" ]]; then
echo "unexpected Local operator image contract: ${actual}" >&2
exit 1
fi
- name: Verify the opt-in Local Console identity
env:
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} 65532:65532 edge-application-api,standalone-application-api offline-loopback
run: |
set -euo pipefail
actual="$(docker image inspect --format '{{.Architecture}} {{.Config.User}} {{index .Config.Labels "io.qinglong.profile"}} {{index .Config.Labels "io.qinglong.local.console"}}' "${CONSOLE_IMAGE}")"
if [[ "${actual}" != "${EXPECTED}" ]]; then
echo "unexpected Local Console image contract: ${actual}" >&2
exit 1
fi
- name: Reconcile the exact production package inventory
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--network none
--cap-drop ALL
--security-opt no-new-privileges
--volume "${{ github.workspace }}:/audit:ro"
--workdir /audit
--entrypoint node
"${IMAGE}"
scripts/ql3-local-image-inventory.cjs
--inventory-root=/opt/qinglong/node_modules
- name: Reconcile the short-lived Local operator package inventory
env:
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--network none
--cap-drop ALL
--security-opt no-new-privileges
--volume "${{ github.workspace }}:/audit:ro"
--workdir /audit
--entrypoint node
"${OPERATOR_IMAGE}"
scripts/ql3-local-operator-image-inventory.cjs
--inventory-root=/opt/qinglong/node_modules
- name: Reconcile the opt-in Local Console package inventory
env:
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--network none
--cap-drop ALL
--security-opt no-new-privileges
--volume "${{ github.workspace }}:/audit:ro"
--workdir /audit
--entrypoint node
"${CONSOLE_IMAGE}"
scripts/ql3-local-console-image-inventory.cjs
--inventory-root=/opt/qinglong/node_modules
- name: Generate and reconcile the reviewed CycloneDX SBOM
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-cluster-image-sbom.cjs \
--image=local \
--output="${{ runner.temp }}/ql3-local-application.cdx.json"
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--volume "${{ github.workspace }}:/audit:ro" \
--workdir /audit \
--entrypoint node \
"${IMAGE}" \
scripts/ql3-cluster-image-sbom.cjs \
--image=local \
--inventory-root=/opt/qinglong/node_modules
- name: Generate and reconcile the Local operator CycloneDX SBOM
env:
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-cluster-image-sbom.cjs \
--image=local-operator \
--output="${{ runner.temp }}/ql3-local-operator.cdx.json"
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--volume "${{ github.workspace }}:/audit:ro" \
--workdir /audit \
--entrypoint node \
"${OPERATOR_IMAGE}" \
scripts/ql3-cluster-image-sbom.cjs \
--image=local-operator \
--inventory-root=/opt/qinglong/node_modules
- name: Generate and reconcile the Local Console CycloneDX SBOM
env:
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-cluster-image-sbom.cjs \
--image=local-console \
--output="${{ runner.temp }}/ql3-local-console.cdx.json"
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--volume "${{ github.workspace }}:/audit:ro" \
--workdir /audit \
--entrypoint node \
"${CONSOLE_IMAGE}" \
scripts/ql3-cluster-image-sbom.cjs \
--image=local-console \
--inventory-root=/opt/qinglong/node_modules
- name: Exercise the entrypoint under the router stress envelope
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--network none
--cap-drop ALL
--security-opt no-new-privileges
--memory=128m
--memory-swap=128m
--cpus=0.5
--pids-limit=64
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=16m
"${IMAGE}"
--help
- name: Exercise the opt-in Console entrypoint under the router stress envelope
env:
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--network none
--cap-drop ALL
--security-opt no-new-privileges
--memory=128m
--memory-swap=128m
--cpus=0.5
--pids-limit=64
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=16m
"${CONSOLE_IMAGE}"
--help
- name: Exercise the short-lived Local operator entrypoint
env:
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: |
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--memory=128m \
--memory-swap=128m \
--cpus=0.5 \
--pids-limit=32 \
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=8m \
"${OPERATOR_IMAGE}" --version
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--memory=128m \
--memory-swap=128m \
--cpus=0.5 \
--pids-limit=32 \
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=8m \
"${OPERATOR_IMAGE}" setup --help
docker run --rm --read-only \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--entrypoint /usr/bin/docker \
"${OPERATOR_IMAGE}" --version
- name: Build the short-lived fresh deployment authority
working-directory: packages/ql3-local-owner-cli
run: node ../../scripts/ql3-build-package-closure.cjs
- name: Run fresh Edge and Standalone SQLite lifecycles
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-local-image-live-contract.cjs --image="${IMAGE}" --profile=edge
node scripts/ql3-local-image-live-contract.cjs --image="${IMAGE}" --profile=standalone
- name: Run the downloadable Local Alpha trial kit journey
env:
IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-local-alpha-trial-kit-live-contract.cjs \
--application-image="${IMAGE}" \
--operator-image="${OPERATOR_IMAGE}" \
--profile=edge \
--variant=headless
node scripts/ql3-local-alpha-trial-kit-live-contract.cjs \
--application-image="${IMAGE}" \
--operator-image="${OPERATOR_IMAGE}" \
--profile=standalone \
--variant=headless
- name: Run the Local Console loopback journey
env:
IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
run: |
node scripts/ql3-local-alpha-trial-kit-live-contract.cjs \
--application-image="${IMAGE}" \
--operator-image="${OPERATOR_IMAGE}" \
--profile=edge \
--variant=console
- name: Run authenticated Local API cancellation through real Linux processes
env:
QL3_LOCAL_API_CANCELLATION_LIVE: '1'
run: |
install -d -m 0700 "${{ runner.temp }}/ql3-local-api-cancellation"
node scripts/ql3-local-api-cancellation-live-contract.cjs \
--profile=edge \
--report="${{ runner.temp }}/ql3-local-api-cancellation/edge.json"
node scripts/ql3-local-api-cancellation-live-contract.cjs \
--profile=standalone \
--report="${{ runner.temp }}/ql3-local-api-cancellation/standalone.json"
node scripts/ql3-local-api-cancellation-live-audit.cjs \
--report="${{ runner.temp }}/ql3-local-api-cancellation/edge.json"
node scripts/ql3-local-api-cancellation-live-audit.cjs \
--report="${{ runner.temp }}/ql3-local-api-cancellation/standalone.json"
- name: Materialize and offline-audit the native Local Alpha trial kit
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'local' || inputs.alpha_artifact_scope == 'all')
env:
HEADLESS_IMAGE: qinglong3-local-application:ci-${{ matrix.image_arch }}
CONSOLE_IMAGE: qinglong3-local-console:ci-${{ matrix.image_arch }}
OPERATOR_IMAGE: qinglong3-local-operator:ci-${{ matrix.image_arch }}
TRIAL_VARIANT: ${{ inputs.local_alpha_variant }}
run: |
set -euo pipefail
umask 077
if [[ "${TRIAL_VARIANT}" == 'console' ]]; then
IMAGE="${CONSOLE_IMAGE}"
APPLICATION_SBOM="${RUNNER_TEMP}/ql3-local-console.cdx.json"
else
IMAGE="${HEADLESS_IMAGE}"
APPLICATION_SBOM="${RUNNER_TEMP}/ql3-local-application.cdx.json"
fi
BUNDLE_ROOT="${RUNNER_TEMP}/ql3-alpha-local-${TRIAL_VARIANT}-${{ matrix.image_arch }}"
node scripts/ql3-local-alpha-trial-kit-bundle.cjs \
--mode=record-verification \
--application-image="${IMAGE}" \
--operator-image="${OPERATOR_IMAGE}" \
--architecture=${{ matrix.image_arch }} \
--variant="${TRIAL_VARIANT}" \
--source-revision=${{ github.sha }} \
--repository=${{ github.repository }} \
--workflow-ref="${{ github.workflow_ref }}" \
--workflow-sha=${{ github.workflow_sha }} \
--event=${{ github.event_name }} \
--job=${{ github.job }} \
--run-id=${{ github.run_id }} \
--run-attempt=${{ github.run_attempt }} \
--output="${RUNNER_TEMP}/ql3-local-alpha-verification-${{ matrix.image_arch }}.json"
node scripts/ql3-local-alpha-trial-kit-bundle.cjs \
--mode=create \
--application-image="${IMAGE}" \
--operator-image="${OPERATOR_IMAGE}" \
--architecture=${{ matrix.image_arch }} \
--variant="${TRIAL_VARIANT}" \
--source-revision=${{ github.sha }} \
--application-sbom="${APPLICATION_SBOM}" \
--operator-sbom="${RUNNER_TEMP}/ql3-local-operator.cdx.json" \
--verification-evidence="${RUNNER_TEMP}/ql3-local-alpha-verification-${{ matrix.image_arch }}.json" \
--readme=docs/operations/ql3-local-alpha-trial-kit.md \
--output="${BUNDLE_ROOT}"
node scripts/ql3-local-alpha-trial-kit-bundle.cjs \
--mode=audit --bundle="${BUNDLE_ROOT}"
QUICKSTART_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-quickstart-${{ matrix.image_arch }}"
QUICKSTART_ROOT="${RUNNER_TEMP}/ql3-alpha-${TRIAL_VARIANT}-quickstart-${{ matrix.image_arch }}"
trap 'docker rm --force "${QUICKSTART_CONTAINER}" >/dev/null 2>&1 || true' EXIT
sh "${BUNDLE_ROOT}/quickstart.sh" \
edge "${QUICKSTART_ROOT}" "${QUICKSTART_CONTAINER}"
if [[ "${TRIAL_VARIANT}" == 'console' ]]; then
test "$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:5700/)" = '200'
test "$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:5700/api/v3/projects/default/tasks)" = '401'
fi
docker stop --time 30 "${QUICKSTART_CONTAINER}"
docker rm "${QUICKSTART_CONTAINER}"
test -s "${QUICKSTART_ROOT}/qinglong3.sqlite"
test -s "${QUICKSTART_ROOT}/owner-delivery/credential-019f8680-143d-4000-8000-000000000021.ready.json"
LEGACY_READINESS_ROOT="${RUNNER_TEMP}/ql3-alpha-legacy-readiness-${{ matrix.image_arch }}"
UPGRADE_EVIDENCE_ROOT="${RUNNER_TEMP}/ql3-alpha-upgrade-evidence-${{ matrix.image_arch }}"
node scripts/ql3-local-alpha-upgrade-readiness-fixture.cjs \
--output="${LEGACY_READINESS_ROOT}"
sh "${BUNDLE_ROOT}/upgrade-readiness.sh" \
edge "${LEGACY_READINESS_ROOT}" "${UPGRADE_EVIDENCE_ROOT}"
grep -q '"status":"inspected"' \
"${UPGRADE_EVIDENCE_ROOT}/results/sqlite-inspect.result.json"
grep -q '"status":"inspected"' \
"${UPGRADE_EVIDENCE_ROOT}/results/data-directory-inspect.result.json"
SQLITE_PLAN_DIGEST="$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).evidence.planDigest)' \
"${UPGRADE_EVIDENCE_ROOT}/results/sqlite-inspect.result.json")"
DIRECTORY_PLAN_DIGEST="$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).evidence.planDigest)' \
"${UPGRADE_EVIDENCE_ROOT}/results/data-directory-inspect.result.json")"
UPGRADE_REHEARSAL_ROOT="${RUNNER_TEMP}/ql3-alpha-upgrade-cutover-rehearsal-${{ matrix.image_arch }}"
RECONCILIATION_REHEARSAL_ROOT="${RUNNER_TEMP}/ql3-alpha-upgrade-reconciliation-rehearsal-${{ matrix.image_arch }}"
RECONCILIATION_CAPTURE_ROOT="${RUNNER_TEMP}/ql3-alpha-upgrade-reconciliation-capture-${{ matrix.image_arch }}"
LEGACY_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-upgrade-legacy-${{ matrix.image_arch }}"
TARGET_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-upgrade-target-${{ matrix.image_arch }}"
RECONCILIATION_LEGACY_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-reconciliation-legacy-${{ matrix.image_arch }}"
RECONCILIATION_TARGET_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-reconciliation-target-${{ matrix.image_arch }}"
trap 'docker rm --force "${QUICKSTART_CONTAINER}" "${TARGET_CONTAINER}" "${LEGACY_CONTAINER}" "${RECONCILIATION_TARGET_CONTAINER}" "${RECONCILIATION_LEGACY_CONTAINER}" >/dev/null 2>&1 || true' EXIT
sh "${BUNDLE_ROOT}/upgrade-cutover-rehearsal.sh" \
edge "${LEGACY_READINESS_ROOT}" "${UPGRADE_REHEARSAL_ROOT}" \
"${SQLITE_PLAN_DIGEST}" "${DIRECTORY_PLAN_DIGEST}" \
"${LEGACY_CONTAINER}" "${TARGET_CONTAINER}"
grep -q '"status":"verified"' \
"${UPGRADE_REHEARSAL_ROOT}/stage-summary.json"
grep -q '"legacySource":"read_only"' \
"${UPGRADE_REHEARSAL_ROOT}/stage-summary.json"
grep -q '"cutover":"not_authorized"' \
"${UPGRADE_REHEARSAL_ROOT}/stage-summary.json"
grep -q '"status":"rollback_candidate"' \
"${UPGRADE_REHEARSAL_ROOT}/cutover-summary.json"
grep -q '"variant":"'"${TRIAL_VARIANT}"'"' \
"${UPGRADE_REHEARSAL_ROOT}/cutover-summary.json"
grep -q '"legacySource":"unchanged"' \
"${UPGRADE_REHEARSAL_ROOT}/cutover-summary.json"
grep -q '"target":"stopped"' \
"${UPGRADE_REHEARSAL_ROOT}/cutover-summary.json"
docker rm "${TARGET_CONTAINER}" "${LEGACY_CONTAINER}"
sh "${BUNDLE_ROOT}/upgrade-cutover-rehearsal.sh" \
edge "${LEGACY_READINESS_ROOT}" "${RECONCILIATION_REHEARSAL_ROOT}" \
"${SQLITE_PLAN_DIGEST}" "${DIRECTORY_PLAN_DIGEST}" \
"${RECONCILIATION_LEGACY_CONTAINER}" "${RECONCILIATION_TARGET_CONTAINER}" \
--capture-after-write "${RECONCILIATION_CAPTURE_ROOT}"
grep -q '"status":"reconciliation_captured"' \
"${RECONCILIATION_REHEARSAL_ROOT}/reconciliation-capture-summary.json"
grep -q '"operation":"task.put"' \
"${RECONCILIATION_REHEARSAL_ROOT}/reconciliation-capture-summary.json"
grep -q '"rollback":"not_authorized"' \
"${RECONCILIATION_REHEARSAL_ROOT}/reconciliation-capture-summary.json"
grep -q '"next":"review_required"' \
"${RECONCILIATION_REHEARSAL_ROOT}/reconciliation-capture-summary.json"
grep -q '"status":"verified"' \
"${RECONCILIATION_REHEARSAL_ROOT}/results/reconciliation-capture-verify.result.json"
test -s "${RECONCILIATION_CAPTURE_ROOT}/019f8680-143d-4000-8000-000000000161/manifest.json"
test -s "${RECONCILIATION_CAPTURE_ROOT}/019f8680-143d-4000-8000-000000000161/receipt.json"
test -d "${RECONCILIATION_CAPTURE_ROOT}/019f8680-143d-4000-8000-000000000161/assets"
docker rm "${RECONCILIATION_TARGET_CONTAINER}" "${RECONCILIATION_LEGACY_CONTAINER}"
test ! -e "${LEGACY_READINESS_ROOT}/db/database.sqlite-wal"
test ! -e "${LEGACY_READINESS_ROOT}/db/database.sqlite-journal"
trap - EXIT
- name: Upload the tested native Local Alpha trial kit
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'local' || inputs.alpha_artifact_scope == 'all')
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-alpha-${{ github.sha }}-local-${{ inputs.local_alpha_variant }}-${{ matrix.image_arch }}
path: ${{ runner.temp }}/ql3-alpha-local-${{ inputs.local_alpha_variant }}-${{ matrix.image_arch }}
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false
cluster-image:
name: Cluster ${{ matrix.image }} image (${{ matrix.image_arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: control
repository: qinglong3-cluster-control
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: control
repository: qinglong3-cluster-control
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: control-ai
repository: qinglong3-cluster-control-ai
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: control-ai
repository: qinglong3-cluster-control-ai
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: admin
repository: qinglong3-cluster-admin
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: admin
repository: qinglong3-cluster-admin
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: worker
repository: qinglong3-worker
runtime_user: 65532:65532
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: worker
repository: qinglong3-worker
runtime_user: 65532:65532
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.node_arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Test exact SBOM and release contract failures
run: node --test test/back/ql3ClusterImageSbom.test.cjs test/back/ql3ClusterImageReleaseAudit.test.cjs test/back/ql3ClusterCopilotConsoleCapacityEvidence.test.cjs
- name: Audit deployment and image release contracts
run: |
pnpm audit:cluster-deployment:ql3
pnpm audit:image-release:ql3
- name: Build the native production image
env:
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
run: >-
docker build
--file ${{ matrix.dockerfile }}
--target ${{ matrix.target }}
--build-arg SOURCE_REVISION=${{ github.sha }}
--tag "${IMAGE}"
.
- name: Materialize the reviewed Cluster OS vulnerability exceptions
run: >-
node scripts/ql3-image-os-vulnerability-policy.cjs
--image=${{ matrix.image }}
--output=${{ runner.temp }}/ql3-${{ matrix.image }}-${{ matrix.image_arch }}.trivyignore.yaml
- name: Reject unexcepted high or critical Cluster OS vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: 'v0.70.0'
image-ref: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
scanners: 'vuln'
vuln-type: 'os'
severity: 'HIGH,CRITICAL'
ignore-unfixed: 'false'
exit-code: '1'
format: 'table'
hide-progress: 'true'
timeout: '10m0s'
cache: 'false'
trivyignores: ${{ runner.temp }}/ql3-${{ matrix.image }}-${{ matrix.image_arch }}.trivyignore.yaml
- name: Verify architecture and non-root runtime identity
env:
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} ${{ matrix.runtime_user }}
run: |
set -euo pipefail
actual="$(docker image inspect --format '{{.Architecture}} {{.Config.User}}' "${IMAGE}")"
if [[ "${actual}" != "${EXPECTED}" ]]; then
echo "unexpected image contract: ${actual}" >&2
exit 1
fi
- name: Run the bounded Cluster Admin product facade
if: matrix.image == 'admin'
env:
IMAGE: qinglong3-cluster-admin:ci-${{ matrix.image_arch }}
QL3_CLUSTER_ADMIN_PRODUCT_LIVE: '1'
run: node scripts/ql3-cluster-admin-product-live-contract.cjs --image="${IMAGE}"
- name: Capture the fixed Cluster Copilot Console capacity envelope
if: matrix.image == 'admin'
timeout-minutes: 10
env:
IMAGE: qinglong3-cluster-admin:ci-${{ matrix.image_arch }}
QL3_CLUSTER_COPILOT_CONSOLE_CAPACITY_LIVE: '1'
SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_REVISION: ${{ github.sha }}
SOURCE_WORKFLOW: ${{ github.workflow }}
SOURCE_RUN_ID: ${{ github.run_id }}
SOURCE_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
mkdir -p "${RUNNER_TEMP}/ql3-cluster-console-capacity"
node scripts/ql3-cluster-copilot-console-capacity-evidence.cjs \
--mode=capture \
--repository="${SOURCE_REPOSITORY}" \
--revision="${SOURCE_REVISION}" \
--workflow="${SOURCE_WORKFLOW}" \
--run-id="${SOURCE_RUN_ID}" \
--run-attempt="${SOURCE_RUN_ATTEMPT}" \
--architecture="${{ matrix.node_arch }}" \
--image="${IMAGE}" \
--output="${RUNNER_TEMP}/ql3-cluster-console-capacity/${{ matrix.node_arch }}.json"
- name: Upload native Cluster Copilot Console capacity evidence
if: matrix.image == 'admin'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-cluster-console-capacity-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.node_arch }}
path: ${{ runner.temp }}/ql3-cluster-console-capacity/${{ matrix.node_arch }}.json
if-no-files-found: error
retention-days: 14
compression-level: 0
overwrite: false
include-hidden-files: false
- name: Generate the reviewed application SBOM
run: >-
node scripts/ql3-cluster-image-sbom.cjs
--image=${{ matrix.image }}
--output=${{ runner.temp }}/ql3-cluster-${{ matrix.image }}.cdx.json
- name: Reconcile SBOM with the actual read-only image inventory
env:
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--security-opt no-new-privileges
--user ${{ matrix.runtime_user }}
--volume "${{ github.workspace }}:/audit:ro"
--workdir /audit
--entrypoint node
"${IMAGE}"
scripts/ql3-cluster-image-sbom.cjs
--image=${{ matrix.image }}
--inventory-root=/opt/qinglong/node_modules
- name: Package the tested native Cluster Alpha image
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'cluster' || inputs.alpha_artifact_scope == 'all')
env:
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
BUNDLE_ROOT: ${{ runner.temp }}/ql3-alpha-${{ matrix.image }}-${{ matrix.image_arch }}
run: |
set -euo pipefail
umask 077
node scripts/ql3-cluster-alpha-bundle.cjs \
--mode=record-verification \
--role=${{ matrix.image }} \
--architecture=${{ matrix.image_arch }} \
--image="${IMAGE}" \
--source-revision=${{ github.sha }} \
--repository=${{ github.repository }} \
--workflow-ref="${{ github.workflow_ref }}" \
--workflow-sha=${{ github.workflow_sha }} \
--event=${{ github.event_name }} \
--job=cluster-image \
--run-id=${{ github.run_id }} \
--run-attempt=${{ github.run_attempt }} \
--output="${RUNNER_TEMP}/ql3-cluster-${{ matrix.image }}-${{ matrix.image_arch }}-verification.json"
node scripts/ql3-cluster-alpha-bundle.cjs \
--mode=create \
--role=${{ matrix.image }} \
--architecture=${{ matrix.image_arch }} \
--image="${IMAGE}" \
--source-revision=${{ github.sha }} \
--sbom="${RUNNER_TEMP}/ql3-cluster-${{ matrix.image }}.cdx.json" \
--verification-evidence="${RUNNER_TEMP}/ql3-cluster-${{ matrix.image }}-${{ matrix.image_arch }}-verification.json" \
--readme=docs/operations/ql3-alpha-candidate.md \
--output="${BUNDLE_ROOT}"
node scripts/ql3-cluster-alpha-bundle.cjs \
--mode=offline-audit \
--bundle="${BUNDLE_ROOT}"
- name: Upload the tested native Cluster Alpha image
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'cluster' || inputs.alpha_artifact_scope == 'all')
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-alpha-${{ github.sha }}-${{ matrix.image }}-${{ matrix.image_arch }}
path: ${{ runner.temp }}/ql3-alpha-${{ matrix.image }}-${{ matrix.image_arch }}
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false
cluster-console-capacity-release-evidence:
name: Cross-architecture Cluster Copilot Console capacity evidence
needs: cluster-image
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
- name: Download native x64 Console capacity evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-cluster-console-capacity-${{ github.run_id }}-${{ github.run_attempt }}-x64
path: ${{ runner.temp }}/ql3-cluster-console-capacity/x64
- name: Download native arm64 Console capacity evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-cluster-console-capacity-${{ github.run_id }}-${{ github.run_attempt }}-arm64
path: ${{ runner.temp }}/ql3-cluster-console-capacity/arm64
- name: Merge and audit the source-bound Console capacity evidence
env:
SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_REVISION: ${{ github.sha }}
SOURCE_WORKFLOW: ${{ github.workflow }}
SOURCE_RUN_ID: ${{ github.run_id }}
SOURCE_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
node scripts/ql3-cluster-copilot-console-capacity-evidence.cjs \
--mode=merge \
--repository="${SOURCE_REPOSITORY}" \
--revision="${SOURCE_REVISION}" \
--workflow="${SOURCE_WORKFLOW}" \
--run-id="${SOURCE_RUN_ID}" \
--run-attempt="${SOURCE_RUN_ATTEMPT}" \
--x64="${RUNNER_TEMP}/ql3-cluster-console-capacity/x64/x64.json" \
--arm64="${RUNNER_TEMP}/ql3-cluster-console-capacity/arm64/arm64.json" \
--output="${RUNNER_TEMP}/ql3-cluster-console-capacity/cross-architecture.json"
node scripts/ql3-cluster-copilot-console-capacity-evidence.cjs \
--mode=audit \
--repository="${SOURCE_REPOSITORY}" \
--revision="${SOURCE_REVISION}" \
--workflow="${SOURCE_WORKFLOW}" \
--run-id="${SOURCE_RUN_ID}" \
--run-attempt="${SOURCE_RUN_ATTEMPT}" \
--report="${RUNNER_TEMP}/ql3-cluster-console-capacity/cross-architecture.json"
- name: Upload cross-architecture Console capacity evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-cluster-console-capacity-release-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-cluster-console-capacity/cross-architecture.json
if-no-files-found: error
retention-days: 14
compression-level: 0
overwrite: false
include-hidden-files: false
image-oci:
name: ${{ matrix.image }} multi-architecture OCI evidence
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- image: control
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- image: control-ai
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- image: admin
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- image: local
dockerfile: deploy/containers/ql3-local-application/Dockerfile
target: runtime
- image: local-operator
dockerfile: deploy/containers/ql3-local-operator/Dockerfile
target: runtime
- image: worker
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Test bounded OCI layout audit failures
run: node --test test/back/ql3ClusterOciLayoutAudit.test.cjs
- name: Build an attested amd64 and arm64 OCI layout
run: >-
docker buildx build
--platform linux/amd64,linux/arm64
--file ${{ matrix.dockerfile }}
--target ${{ matrix.target }}
--build-arg SOURCE_REVISION=${{ github.sha }}
--attest type=provenance,mode=max
--attest type=sbom
--output type=oci,dest=${{ runner.temp }}/ql3-${{ matrix.image }}.oci.tar
.
- name: Audit both image manifests and bound attestations
run: |
mkdir "${{ runner.temp }}/ql3-${{ matrix.image }}-oci"
tar -xf "${{ runner.temp }}/ql3-${{ matrix.image }}.oci.tar" \
-C "${{ runner.temp }}/ql3-${{ matrix.image }}-oci"
node scripts/ql3-cluster-oci-layout-audit.cjs \
--image="${{ matrix.image }}" \
--layout="${{ runner.temp }}/ql3-${{ matrix.image }}-oci" \
--expected-revision="${{ github.sha }}"
worker-runtime:
name: Worker runtime (${{ matrix.arch }}, Node 24)
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
- runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Initialize the public test environment
run: cp .env.example .env
- name: Audit profile dependency boundaries
run: |
pnpm audit:edge-imports:ql3
pnpm audit:cluster-dependencies:ql3
- name: Build QL3 workspace once
run: pnpm run build:packages:ql3
- name: Test local identity, atomic install and explicit renewal
run: node --test packages/ql3-worker-runtime/test/*.test.cjs
local-profiles:
name: Local Profiles (${{ matrix.arch }}, Node 24)
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
- runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit exact Profile dependency and import boundaries
run: pnpm audit:cluster-dependencies:ql3
- name: Test bounded QL3 artifact cleanup scope
run: node --test test/back/ql3CleanPackageArtifacts.test.cjs
- name: Build once and test all QL3 packages
run: pnpm run test:packages:ql3
- name: Exercise Owner authorities as root in a read-only container
run: >-
docker run --rm --read-only
--memory=192m
--memory-swap=192m
--cpus=1
--pids-limit=64
--tmpfs /tmp:rw,nosuid,nodev,mode=1777,size=64m
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:24.18.0-bookworm-slim
node scripts/ql3-local-owner-readonly-contract.cjs --mode=root
- name: Exercise Owner authorities as non-root in a read-only container
run: >-
docker run --rm --read-only
--user 65532:65532
--memory=192m
--memory-swap=192m
--cpus=1
--pids-limit=64
--tmpfs /tmp:rw,nosuid,nodev,mode=1777,size=64m
--volume "${{ github.workspace }}:/workspace:ro"
--workdir /workspace
node:24.18.0-bookworm-slim
node scripts/ql3-local-owner-readonly-contract.cjs --mode=nonroot
- name: Build and audit production-only local Profile artifacts
run: |
pnpm audit:artifact:edge:ql3
pnpm audit:artifact:standalone:ql3
pnpm audit:artifact:edge-adopted:ql3
pnpm audit:artifact:standalone-adopted:ql3
pnpm audit:artifact:edge-application:ql3
pnpm audit:artifact:standalone-application:ql3
pnpm audit:artifact:edge-application-api:ql3
pnpm audit:artifact:standalone-application-api:ql3
pnpm audit:artifact:edge-ai:ql3
pnpm audit:artifact:standalone-ai:ql3
cluster-postgres:
name: Cluster PostgreSQL ${{ matrix.postgres }} (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
postgres: '16'
- runner: ubuntu-24.04
arch: x64
postgres: '18'
- runner: ubuntu-24.04-arm
arch: arm64
postgres: '16'
- runner: ubuntu-24.04-arm
arch: arm64
postgres: '18'
services:
postgres:
image: postgres:${{ matrix.postgres }}
env:
POSTGRES_DB: ql3_contract
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d ql3_contract"
--health-interval 5s
--health-timeout 5s
--health-retries 12
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit exact cluster dependency graph
run: pnpm audit:cluster-dependencies:ql3
- name: Build QL3 workspace once
run: pnpm run build:packages:ql3
- name: Test profile-neutral runtime contracts
run: node --test packages/ql3-runtime-core/test/*.test.cjs
- name: Test optional bounded AI gateway and remote provider contracts
run: node --test packages/ql3-ai/test/*.test.cjs
- name: Build and test lazy pg.Pool binding
run: node --test packages/ql3-cluster-postgres/test/*.test.cjs
- name: Test readiness-first cluster composition root
run: node --test packages/ql3-cluster-control/test/*.test.cjs
- name: Test isolated short-lived cluster administration root
run: node --test packages/ql3-cluster-admin/test/*.test.cjs
- name: Provision unprivileged database roles before migration
run: |
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_migration LOGIN PASSWORD 'ql3_migration_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_runtime LOGIN PASSWORD 'ql3_runtime_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_admin LOGIN PASSWORD 'ql3_admin_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_ai_maintenance LOGIN PASSWORD 'ql3_ai_maintenance_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_ai_credential_manager LOGIN PASSWORD 'ql3_ai_credential_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_ai_credential_tester LOGIN PASSWORD 'ql3_ai_credential_tester_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_automation_manager LOGIN PASSWORD 'ql3_automation_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_approval_manager LOGIN PASSWORD 'ql3_approval_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_run_manager LOGIN PASSWORD 'ql3_run_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_manager LOGIN PASSWORD 'ql3_package_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_package_executor LOGIN PASSWORD 'ql3_package_executor_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_worker_credential_manager LOGIN PASSWORD 'ql3_worker_credential_manager_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_worker_credential_executor LOGIN PASSWORD 'ql3_worker_credential_executor_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d ql3_contract -v ON_ERROR_STOP=1 -c "CREATE ROLE ql3_worker_ingress LOGIN PASSWORD 'ql3_worker_ingress_test'"
docker exec ${{ job.services.postgres.id }} psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "ALTER DATABASE ql3_contract OWNER TO ql3_migration"
- name: Create schema and exact grants through the reviewed migration stream
env:
QL3_POSTGRES_MIGRATION_URL: postgresql://ql3_migration:ql3_migration_test@127.0.0.1:5432/ql3_contract
QL3_POSTGRES_TLS_MODE: disable
QL3_POSTGRES_ALLOW_INSECURE: 'true'
run: node packages/ql3-cluster-postgres/dist/migration/migrationCli.js
- name: Test readiness, isolated roles, shared Repositories, rollback and SQLSTATE mapping
env:
QL3_TEST_POSTGRES_MIGRATION_URL: postgresql://ql3_migration:ql3_migration_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_RUNTIME_URL: postgresql://ql3_runtime:ql3_runtime_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_ADMIN_URL: postgresql://ql3_admin:ql3_admin_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_AUTOMATION_MANAGER_URL: postgresql://ql3_automation_manager:ql3_automation_manager_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_RUN_MANAGER_URL: postgresql://ql3_run_manager:ql3_run_manager_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_PACKAGE_MANAGER_URL: postgresql://ql3_package_manager:ql3_package_manager_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_PACKAGE_EXECUTOR_URL: postgresql://ql3_package_executor:ql3_package_executor_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_WORKER_INGRESS_URL: postgresql://ql3_worker_ingress:ql3_worker_ingress_test@127.0.0.1:5432/ql3_contract
run: node --test --test-concurrency=1 packages/ql3-cluster-postgres/test/postgres.integration.test.cjs
- name: Test automation management concurrency and post-commit response loss over mTLS
env:
QL3_TEST_POSTGRES_URL: postgresql://ql3_migration:ql3_migration_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_AUTOMATION_MANAGER_URL: postgresql://ql3_automation_manager:ql3_automation_manager_test@127.0.0.1:5432/ql3_contract
run: pnpm --filter @qinglong/cluster-admin test:automation-integration
- name: Test bootstrap-owned cluster recovery before admission
env:
QL3_TEST_POSTGRES_MIGRATION_URL: postgresql://ql3_migration:ql3_migration_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_RUNTIME_URL: postgresql://ql3_runtime:ql3_runtime_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_FAULT_INJECTION_URL: postgresql://postgres:postgres@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_WORKER_INGRESS_URL: postgresql://ql3_worker_ingress:ql3_worker_ingress_test@127.0.0.1:5432/ql3_contract
run: node --test packages/ql3-cluster-control/test/postgres.integration.test.cjs
- name: Test optional AI invocation and price catalog against isolated roles
env:
QL3_TEST_POSTGRES_MIGRATION_URL: postgresql://ql3_migration:ql3_migration_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_RUNTIME_URL: postgresql://ql3_runtime:ql3_runtime_test@127.0.0.1:5432/ql3_contract
QL3_TEST_POSTGRES_ADMIN_URL: postgresql://ql3_admin:ql3_admin_test@127.0.0.1:5432/ql3_contract
run: >-
node --test --test-concurrency=1
packages/ql3-ai/test/postgresModelInvocationRepository.integration.test.cjs
packages/ql3-ai/test/postgresModelPriceCatalogRepository.integration.test.cjs
cluster-postgres-ha:
name: PostgreSQL 18 physical promotion (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
- runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit exact cluster dependency graph
run: pnpm audit:cluster-dependencies:ql3
- name: Build QL3 workspace once
run: pnpm run build:packages:ql3
- name: Rotate PostgreSQL TLS trust through overlap and rollback
run: pnpm test:postgres-tls-rotation:ql3
- name: Fence the old primary, promote the standby and reactivate two controls
env:
QL3_HA_REPORT: ${{ runner.temp }}/ql3-postgres-ha/report.json
run: |
install -d -m 0700 "${RUNNER_TEMP}/ql3-postgres-ha"
pnpm test:postgres-ha:ql3
pnpm audit:postgres-ha-evidence:ql3 -- \
--report="${RUNNER_TEMP}/ql3-postgres-ha/report.json"
- name: Upload PostgreSQL HA evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-postgres-ha-${{ matrix.arch }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-postgres-ha/report.json
if-no-files-found: warn
retention-days: 14
cluster-cloudnativepg-live:
name: CloudNativePG live failover contract
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install verified kind v0.31.0
run: |
curl --fail --location --silent --show-error \
--output kind-linux-amd64 \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
curl --fail --location --silent --show-error \
--output kind.sha256sum \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64.sha256sum
sha256sum --check kind.sha256sum
chmod +x kind-linux-amd64
- name: Install verified kubectl v1.32.8
run: |
curl --fail --location --silent --show-error \
--output kubectl \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output kubectl.sha256 \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl.sha256
test "$(cat kubectl.sha256)" = "$(sha256sum kubectl | cut -d ' ' -f 1)"
chmod +x kubectl
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build migration evidence dependency
run: pnpm run build:packages:ql3
- name: Audit the locked CloudNativePG deployment contract
run: |
pnpm audit:cloudnativepg:ql3
pnpm audit:cloudnativepg-backup:ql3
pnpm audit:barman-cloud-supply-chain:ql3
pnpm audit:cert-manager-selection:ql3
kubectl kustomize deploy/kubernetes/ql3-cluster/operations/cloudnative-pg-restore >/dev/null
- name: Prove operator, roles, migration, runtime and fenced node failover
env:
QL3_CLOUDNATIVEPG_LIVE: '1'
QL3_KIND_BIN: ${{ github.workspace }}/kind-linux-amd64
QL3_KUBECTL_BIN: ${{ github.workspace }}/kubectl
QL3_KIND_CLUSTER: ql3-cnpg-ci
run: pnpm test:cloudnativepg-live:ql3
cluster-provider-credential-test-kubernetes-live:
name: Provider credential test K3s and CloudNativePG live contract
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install verified kubectl v1.34.3
run: |
curl --fail --location --silent --show-error \
--output kubectl \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output kubectl.sha256 \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl.sha256
test "$(cat kubectl.sha256)" = "$(sha256sum kubectl | cut -d ' ' -f 1)"
chmod +x kubectl
- name: Fetch locked K3s and CloudNativePG release manifest
run: |
docker pull rancher/k3s:v1.34.3-k3s1
curl --fail --location --silent --show-error \
--output cnpg-1.30.0.yaml \
https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.30.0/cnpg-1.30.0.yaml
echo "f8bede43fe4ee0d478c2355b204a36876b2ae4faac60f2a9452280b293da3b88 cnpg-1.30.0.yaml" | sha256sum --check
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build QL3 workspace once
run: pnpm run build:packages:ql3
- name: Audit the static provider credential test deployment
run: pnpm audit:provider-credential-test-deployment:ql3
- name: Prove eight isolated Jobs, material and CIDR rotation, and database failover
env:
QL3_PROVIDER_CREDENTIAL_TEST_KUBERNETES_LIVE: '1'
QL3_CNPG_OPERATOR_MANIFEST_FILE: ${{ github.workspace }}/cnpg-1.30.0.yaml
QL3_KUBECTL_BIN: ${{ github.workspace }}/kubectl
run: pnpm test:provider-credential-test-kubernetes-live:ql3
cluster-secret-binding-mounted-provider-kubernetes-live:
name: Secret binding approval and mounted provider rotation live contract
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install verified kubectl v1.34.3
run: |
curl --fail --location --silent --show-error \
--output kubectl \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output kubectl.sha256 \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl.sha256
test "$(cat kubectl.sha256)" = "$(sha256sum kubectl | cut -d ' ' -f 1)"
chmod +x kubectl
- name: Preload digest-bound K3s and PostgreSQL fixtures
run: |
docker pull rancher/k3s@sha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c
docker tag \
rancher/k3s@sha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c \
rancher/k3s:v1.34.3-k3s1
docker pull postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296
docker tag \
postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296 \
postgres:18.4-bookworm
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build QL3 workspace once
run: pnpm run build:packages:ql3
- name: Verify the backward-compatible offline evidence contract
run: node --test test/back/ql3PluginPackageSecretBindingKubernetesLiveAudit.test.cjs
- name: Prove approval, two-replica rotation and missing projection fail-close
env:
QL3_PLUGIN_PACKAGE_SECRET_BINDING_KUBERNETES_LIVE: '1'
QL3_KUBECTL_BIN: ${{ github.workspace }}/kubectl
QL3_SECRET_BINDING_LIVE_REPORT: ${{ runner.temp }}/ql3-secret-binding/report.json
run: |
umask 077
mkdir -m 0700 "$(dirname "${QL3_SECRET_BINDING_LIVE_REPORT}")"
pnpm test:plugin-package-secret-binding-kubernetes-live:ql3 \
"--report=${QL3_SECRET_BINDING_LIVE_REPORT}"
- name: Re-audit content-free evidence and isolated cleanup
env:
QL3_SECRET_BINDING_LIVE_REPORT: ${{ runner.temp }}/ql3-secret-binding/report.json
run: |
test "$(stat -c '%a' "${QL3_SECRET_BINDING_LIVE_REPORT}")" = '600'
pnpm audit:plugin-package-secret-binding-kubernetes-live:ql3 \
"--report=${QL3_SECRET_BINDING_LIVE_REPORT}"
sha256sum "${QL3_SECRET_BINDING_LIVE_REPORT}"
test -z "$(docker ps -aq --filter name=ql3-secret-binding-live-)"
- name: Upload audited content-free mounted provider evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-secret-binding-provider-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-secret-binding/report.json
if-no-files-found: error
retention-days: 14
compression-level: 9
overwrite: false
include-hidden-files: false
cluster-vault-kv-worker-secret-live:
name: Vault KV Worker Secret direct custody (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
kubernetesArch: amd64
- runner: ubuntu-24.04-arm
arch: arm64
kubernetesArch: arm64
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Verify native runner architecture
run: node -e "if (process.arch !== '${{ matrix.arch }}') throw new Error('unexpected architecture ' + process.arch)"
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Install verified kubectl v1.34.3
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl" \
"https://dl.k8s.io/release/v1.34.3/bin/linux/${{ matrix.kubernetesArch }}/kubectl"
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl.sha256" \
"https://dl.k8s.io/release/v1.34.3/bin/linux/${{ matrix.kubernetesArch }}/kubectl.sha256"
test "$(cat "${RUNNER_TEMP}/kubectl.sha256")" = \
"$(sha256sum "${RUNNER_TEMP}/kubectl" | cut -d ' ' -f 1)"
chmod 0755 "${RUNNER_TEMP}/kubectl"
- name: Build the direct custody adapter
run: >-
pnpm -r --workspace-concurrency=1
--filter @qinglong/cluster-control...
run build
- name: Verify the fail-closed adapter and evidence contracts
run: |
node --test \
packages/ql3-cluster-control/test/vaultKvSecretProvider.test.cjs \
packages/ql3-cluster-control/test/vaultKvWorkerIngressConfig.test.cjs \
test/back/ql3VaultKvWorkerSecretLiveAudit.test.cjs \
test/back/ql3VaultKvWorkerSecretDeploymentAudit.test.cjs
pnpm audit:vault-kv-worker-secret-deployment:ql3
"${RUNNER_TEMP}/kubectl" kustomize \
deploy/kubernetes/ql3-cluster/vault-kv-worker-secret \
>/dev/null
- name: Preload the digest-pinned native Vault image
run: docker pull docker.io/hashicorp/vault@sha256:4e33b126a59c0c333b76fb4e894722462659a6bec7c48c9ee8cea56fccfd2569
- name: Prove TLS, least privilege, value and token rotation, seal and recovery
env:
QL3_RUN_VAULT_KV_WORKER_SECRET_LIVE: 'true'
QL3_VAULT_KV_WORKER_SECRET_REPORT: ${{ runner.temp }}/ql3-vault-kv-worker-secret/report.json
run: |
umask 077
mkdir -m 0700 "$(dirname "${QL3_VAULT_KV_WORKER_SECRET_REPORT}")"
node scripts/ql3-vault-kv-worker-secret-live-contract.cjs >/dev/null
- name: Re-audit content-free direct custody evidence and cleanup
env:
QL3_VAULT_KV_WORKER_SECRET_REPORT: ${{ runner.temp }}/ql3-vault-kv-worker-secret/report.json
run: |
test "$(stat -c '%a' "${QL3_VAULT_KV_WORKER_SECRET_REPORT}")" = '600'
pnpm audit:vault-kv-worker-secret-live:ql3 \
"--report=${QL3_VAULT_KV_WORKER_SECRET_REPORT}"
sha256sum "${QL3_VAULT_KV_WORKER_SECRET_REPORT}"
test -z "$(docker ps -aq --filter name=ql3-vault-kv-worker-)"
- name: Upload audited content-free Vault KV custody evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-vault-kv-worker-secret-${{ matrix.arch }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-vault-kv-worker-secret/report.json
if-no-files-found: error
retention-days: 14
compression-level: 9
overwrite: false
include-hidden-files: false
cluster-plugin-package-kubernetes-live:
name: Plugin Package Kubernetes CAS and RBAC live contract
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
- name: Install verified kind v0.31.0
run: |
curl --fail --location --silent --show-error \
--output kind-linux-amd64 \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
curl --fail --location --silent --show-error \
--output kind.sha256sum \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64.sha256sum
sha256sum --check kind.sha256sum
chmod +x kind-linux-amd64
- name: Install verified kubectl v1.32.8
run: |
curl --fail --location --silent --show-error \
--output kubectl \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output kubectl.sha256 \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl.sha256
test "$(cat kubectl.sha256)" = "$(sha256sum kubectl | cut -d ' ' -f 1)"
chmod +x kubectl
- name: Prove two restricted recovery Pods converge without overreach
env:
QL3_PLUGIN_PACKAGE_KUBERNETES_LIVE: '1'
QL3_KIND_BIN: ${{ github.workspace }}/kind-linux-amd64
QL3_KUBECTL_BIN: ${{ github.workspace }}/kubectl
QL3_KIND_CLUSTER: ql3-plugin-activation-ci
run: pnpm test:plugin-package-kubernetes-live:ql3
cluster-plugin-package-recovery-e2e:
name: Plugin Package PostgreSQL OCI Kubernetes recovery E2E
runs-on: ubuntu-24.04
timeout-minutes: 35
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install verified kind v0.31.0
run: |
curl --fail --location --silent --show-error \
--output kind-linux-amd64 \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
curl --fail --location --silent --show-error \
--output kind.sha256sum \
https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64.sha256sum
sha256sum --check kind.sha256sum
chmod +x kind-linux-amd64
- name: Install verified kubectl v1.32.8
run: |
curl --fail --location --silent --show-error \
--output kubectl \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output kubectl.sha256 \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl.sha256
test "$(cat kubectl.sha256)" = "$(sha256sum kubectl | cut -d ' ' -f 1)"
chmod +x kubectl
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Pull exact PostgreSQL 18.4 fixture image
run: >-
docker pull
postgres:18.4-bookworm@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296
- name: Prove migration, durable OCI recovery and rollout ordering
env:
QL3_PLUGIN_PACKAGE_RECOVERY_E2E_LIVE: '1'
QL3_SOURCE_REVISION: ${{ github.sha }}
QL3_KIND_BIN: ${{ github.workspace }}/kind-linux-amd64
QL3_KUBECTL_BIN: ${{ github.workspace }}/kubectl
QL3_KIND_CLUSTER: ql3-plugin-recovery-e2e-ci
run: |
install -d -m 0700 "${RUNNER_TEMP}/ql3-plugin-package-recovery-e2e"
pnpm test:plugin-package-recovery-e2e:ql3 \
--report="${RUNNER_TEMP}/ql3-plugin-package-recovery-e2e/report.json"
pnpm audit:plugin-package-recovery-e2e:ql3 \
--report="${RUNNER_TEMP}/ql3-plugin-package-recovery-e2e/report.json"
- name: Upload Plugin Package recovery E2E evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-plugin-package-recovery-e2e-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-plugin-package-recovery-e2e/report.json
if-no-files-found: warn
retention-days: 14
cluster-alpha-milestone:
name: Finalize the Cluster Alpha integration milestone
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'cluster' || inputs.alpha_artifact_scope == 'all')
needs:
- backend
- service-manager-bridge
- linux-resource-envelopes
- linux-resource-release-evidence
- supply-chain
- local-image
- cluster-image
- cluster-console-capacity-release-evidence
- image-oci
- worker-runtime
- local-profiles
- cluster-postgres
- cluster-postgres-ha
- cluster-cloudnativepg-live
- cluster-provider-credential-test-kubernetes-live
- cluster-secret-binding-mounted-provider-kubernetes-live
- cluster-vault-kv-worker-secret-live
- cluster-plugin-package-kubernetes-live
- cluster-plugin-package-recovery-e2e
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download the exact control amd64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-control-amd64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/control-amd64
- name: Download the exact control arm64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-control-arm64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/control-arm64
- name: Download the exact control-ai amd64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-control-ai-amd64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/control-ai-amd64
- name: Download the exact control-ai arm64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-control-ai-arm64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/control-ai-arm64
- name: Download the exact admin amd64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-admin-amd64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/admin-amd64
- name: Download the exact admin arm64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-admin-arm64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/admin-arm64
- name: Download the exact worker amd64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-worker-amd64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/worker-amd64
- name: Download the exact worker arm64 Cluster Alpha bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-worker-arm64
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone-input/worker-arm64
- name: Close all native Cluster bundles into one milestone index
run: |
set -euo pipefail
umask 077
node scripts/ql3-cluster-alpha-milestone.cjs \
--mode=finalize \
--control-amd64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/control-amd64" \
--control-arm64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/control-arm64" \
--control-ai-amd64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/control-ai-amd64" \
--control-ai-arm64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/control-ai-arm64" \
--admin-amd64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/admin-amd64" \
--admin-arm64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/admin-arm64" \
--worker-amd64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/worker-amd64" \
--worker-arm64-bundle="${RUNNER_TEMP}/ql3-alpha-cluster-milestone-input/worker-arm64" \
--source-revision=${{ github.sha }} \
--repository=${{ github.repository }} \
--workflow-ref="${{ github.workflow_ref }}" \
--workflow-sha=${{ github.workflow_sha }} \
--event=${{ github.event_name }} \
--run-id=${{ github.run_id }} \
--run-attempt=${{ github.run_attempt }} \
--readme=docs/operations/ql3-cluster-alpha-milestone.md \
--output="${RUNNER_TEMP}/ql3-alpha-cluster-milestone"
node scripts/ql3-cluster-alpha-milestone.cjs \
--mode=audit \
--milestone="${RUNNER_TEMP}/ql3-alpha-cluster-milestone"
- name: Upload the closed Cluster Alpha milestone index
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-alpha-${{ github.sha }}-cluster-milestone
path: ${{ runner.temp }}/ql3-alpha-cluster-milestone
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false
local-alpha-milestone:
name: Finalize the Local Alpha milestone
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && (inputs.alpha_artifact_scope == 'local' || inputs.alpha_artifact_scope == 'all')
needs:
- backend
- service-manager-bridge
- linux-resource-envelopes
- linux-resource-release-evidence
- supply-chain
- local-image
- cluster-image
- cluster-console-capacity-release-evidence
- image-oci
- worker-runtime
- local-profiles
- cluster-postgres
- cluster-postgres-ha
- cluster-cloudnativepg-live
- cluster-provider-credential-test-kubernetes-live
- cluster-secret-binding-mounted-provider-kubernetes-live
- cluster-vault-kv-worker-secret-live
- cluster-plugin-package-kubernetes-live
- cluster-plugin-package-recovery-e2e
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download the exact amd64 Local Alpha trial kit
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-local-${{ inputs.local_alpha_variant }}-amd64
path: ${{ runner.temp }}/ql3-alpha-local-milestone-input/amd64
- name: Download the exact arm64 Local Alpha trial kit
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-local-${{ inputs.local_alpha_variant }}-arm64
path: ${{ runner.temp }}/ql3-alpha-local-milestone-input/arm64
- name: Close both native trial kits into one milestone index
run: |
set -euo pipefail
umask 077
node scripts/ql3-local-alpha-milestone.cjs \
--mode=finalize \
--amd64-bundle="${RUNNER_TEMP}/ql3-alpha-local-milestone-input/amd64" \
--arm64-bundle="${RUNNER_TEMP}/ql3-alpha-local-milestone-input/arm64" \
--variant=${{ inputs.local_alpha_variant }} \
--source-revision=${{ github.sha }} \
--repository=${{ github.repository }} \
--workflow-ref="${{ github.workflow_ref }}" \
--workflow-sha=${{ github.workflow_sha }} \
--event=${{ github.event_name }} \
--run-id=${{ github.run_id }} \
--run-attempt=${{ github.run_attempt }} \
--readme=docs/operations/ql3-local-alpha-milestone.md \
--output="${RUNNER_TEMP}/ql3-alpha-local-milestone"
node scripts/ql3-local-alpha-milestone.cjs \
--mode=audit \
--milestone="${RUNNER_TEMP}/ql3-alpha-local-milestone"
- name: Upload the closed Local Alpha milestone index
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-alpha-${{ github.sha }}-local-${{ inputs.local_alpha_variant }}-milestone
path: ${{ runner.temp }}/ql3-alpha-local-milestone
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false
alpha-stage-index:
name: Finalize the cross-profile Alpha stage index
if: github.event_name == 'workflow_dispatch' && inputs.produce_alpha_artifacts && inputs.alpha_artifact_scope == 'all'
needs:
- local-alpha-milestone
- cluster-alpha-milestone
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download the closed Local Alpha milestone
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-local-${{ inputs.local_alpha_variant }}-milestone
path: ${{ runner.temp }}/ql3-alpha-stage-input/local
- name: Download the closed Cluster Alpha milestone
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ql3-alpha-${{ github.sha }}-cluster-milestone
path: ${{ runner.temp }}/ql3-alpha-stage-input/cluster
- name: Close both deployment classes into one stage index
run: |
set -euo pipefail
umask 077
node scripts/ql3-alpha-stage-index.cjs \
--mode=finalize \
--local-milestone="${RUNNER_TEMP}/ql3-alpha-stage-input/local" \
--cluster-milestone="${RUNNER_TEMP}/ql3-alpha-stage-input/cluster" \
--source-revision=${{ github.sha }} \
--repository=${{ github.repository }} \
--workflow-ref="${{ github.workflow_ref }}" \
--workflow-sha=${{ github.workflow_sha }} \
--event=${{ github.event_name }} \
--run-id=${{ github.run_id }} \
--run-attempt=${{ github.run_attempt }} \
--readme=docs/operations/ql3-alpha-stage-index.md \
--output="${RUNNER_TEMP}/ql3-alpha-stage-index"
node scripts/ql3-alpha-stage-index.cjs \
--mode=audit \
--stage="${RUNNER_TEMP}/ql3-alpha-stage-index" \
--local-milestone="${RUNNER_TEMP}/ql3-alpha-stage-input/local" \
--cluster-milestone="${RUNNER_TEMP}/ql3-alpha-stage-input/cluster"
- name: Upload the closed cross-profile Alpha stage index
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-alpha-${{ github.sha }}-stage-index
path: ${{ runner.temp }}/ql3-alpha-stage-index
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false