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 }}" RECONCILIATION_FLOW_ROOT="${RUNNER_TEMP}/ql3-alpha-reconciliation-flow-${{ matrix.image_arch }}" RECONCILIATION_DECISION_ROOT="${RUNNER_TEMP}/ql3-alpha-reconciliation-decisions-${{ matrix.image_arch }}" RECONCILIATION_REVIEW_DECISION_ROOT="${RECONCILIATION_DECISION_ROOT}/review" RECONCILIATION_AUTOMATION_DECISION_ROOT="${RECONCILIATION_DECISION_ROOT}/automation" COMPLETION_LEGACY_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-legacy-${{ matrix.image_arch }}" COMPLETION_UPGRADE_EVIDENCE_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-upgrade-evidence-${{ matrix.image_arch }}" COMPLETION_REHEARSAL_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-rehearsal-${{ matrix.image_arch }}" COMPLETION_CAPTURE_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-capture-${{ matrix.image_arch }}" COMPLETION_FLOW_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-flow-${{ matrix.image_arch }}" COMPLETION_DECISION_ROOT="${RUNNER_TEMP}/ql3-alpha-completion-decisions-${{ matrix.image_arch }}" COMPLETION_REVIEW_DECISION_ROOT="${COMPLETION_DECISION_ROOT}/review" COMPLETION_AUTOMATION_DECISION_ROOT="${COMPLETION_DECISION_ROOT}/automation" COMPLETION_SECRET_CONFIG_DECISION_ROOT="${COMPLETION_DECISION_ROOT}/secret-config" 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 }}" COMPLETION_LEGACY_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-completion-legacy-${{ matrix.image_arch }}" COMPLETION_TARGET_CONTAINER="ql3-alpha-${TRIAL_VARIANT}-completion-target-${{ matrix.image_arch }}" trap 'docker rm --force "${QUICKSTART_CONTAINER}" "${TARGET_CONTAINER}" "${LEGACY_CONTAINER}" "${RECONCILIATION_TARGET_CONTAINER}" "${RECONCILIATION_LEGACY_CONTAINER}" "${COMPLETION_TARGET_CONTAINER}" "${COMPLETION_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" install -d -m 0700 \ "${RECONCILIATION_DECISION_ROOT}" \ "${RECONCILIATION_REVIEW_DECISION_ROOT}" \ "${RECONCILIATION_AUTOMATION_DECISION_ROOT}" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ prepare edge "${RECONCILIATION_REHEARSAL_ROOT}" \ "${RECONCILIATION_CAPTURE_ROOT}" "${RECONCILIATION_FLOW_ROOT}" \ Asia/Shanghai grep -q '"status":"operator_decision_required"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"automaticDecision":"not_authorized"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" node scripts/ql3-local-alpha-reconciliation-decision-fixture.cjs \ --mode=review \ --reconciliation-root="${RECONCILIATION_FLOW_ROOT}" \ --output="${RECONCILIATION_REVIEW_DECISION_ROOT}/review.ndjson" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ review edge "${RECONCILIATION_REHEARSAL_ROOT}" \ "${RECONCILIATION_CAPTURE_ROOT}" "${RECONCILIATION_FLOW_ROOT}" \ "${RECONCILIATION_REVIEW_DECISION_ROOT}/review.ndjson" grep -q '"status":"automation_decision_required"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"decisionAuthority":"authenticated_user"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"automaticRowDecision":"not_authorized"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" node scripts/ql3-local-alpha-reconciliation-decision-fixture.cjs \ --mode=automation \ --reconciliation-root="${RECONCILIATION_FLOW_ROOT}" \ --output="${RECONCILIATION_AUTOMATION_DECISION_ROOT}/automation.ndjson" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ apply-rollback edge "${RECONCILIATION_REHEARSAL_ROOT}" \ "${RECONCILIATION_CAPTURE_ROOT}" "${RECONCILIATION_FLOW_ROOT}" \ "${RECONCILIATION_AUTOMATION_DECISION_ROOT}/automation.ndjson" \ "${LEGACY_READINESS_ROOT}" grep -q '"status":"reconciliation_automation_rolled_back"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"completion":"not_attempted"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"targetRestart":"not_attempted"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"legacyRestart":"not_attempted"' \ "${RECONCILIATION_FLOW_ROOT}/summary.json" grep -q '"state":"reconciliation_automation_applied"' \ "${RECONCILIATION_FLOW_ROOT}/results/automation-apply-verify.result.json" grep -q '"state":"reconciliation_automation_rolled_back"' \ "${RECONCILIATION_FLOW_ROOT}/results/automation-rollback-verify.result.json" 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" node scripts/ql3-local-alpha-upgrade-readiness-fixture.cjs \ --shape=completion-ready \ --output="${COMPLETION_LEGACY_ROOT}" sh "${BUNDLE_ROOT}/upgrade-readiness.sh" \ edge "${COMPLETION_LEGACY_ROOT}" "${COMPLETION_UPGRADE_EVIDENCE_ROOT}" COMPLETION_SQLITE_PLAN_DIGEST="$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).evidence.planDigest)' \ "${COMPLETION_UPGRADE_EVIDENCE_ROOT}/results/sqlite-inspect.result.json")" COMPLETION_DIRECTORY_PLAN_DIGEST="$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).evidence.planDigest)' \ "${COMPLETION_UPGRADE_EVIDENCE_ROOT}/results/data-directory-inspect.result.json")" sh "${BUNDLE_ROOT}/upgrade-cutover-rehearsal.sh" \ edge "${COMPLETION_LEGACY_ROOT}" "${COMPLETION_REHEARSAL_ROOT}" \ "${COMPLETION_SQLITE_PLAN_DIGEST}" "${COMPLETION_DIRECTORY_PLAN_DIGEST}" \ "${COMPLETION_LEGACY_CONTAINER}" "${COMPLETION_TARGET_CONTAINER}" \ --capture-after-write "${COMPLETION_CAPTURE_ROOT}" grep -q '"status":"reconciliation_captured"' \ "${COMPLETION_REHEARSAL_ROOT}/reconciliation-capture-summary.json" install -d -m 0700 \ "${COMPLETION_DECISION_ROOT}" \ "${COMPLETION_REVIEW_DECISION_ROOT}" \ "${COMPLETION_AUTOMATION_DECISION_ROOT}" \ "${COMPLETION_SECRET_CONFIG_DECISION_ROOT}" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ prepare edge "${COMPLETION_REHEARSAL_ROOT}" \ "${COMPLETION_CAPTURE_ROOT}" "${COMPLETION_FLOW_ROOT}" \ Asia/Shanghai node scripts/ql3-local-alpha-reconciliation-decision-fixture.cjs \ --mode=completion-review \ --reconciliation-root="${COMPLETION_FLOW_ROOT}" \ --output="${COMPLETION_REVIEW_DECISION_ROOT}/review.ndjson" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ review edge "${COMPLETION_REHEARSAL_ROOT}" \ "${COMPLETION_CAPTURE_ROOT}" "${COMPLETION_FLOW_ROOT}" \ "${COMPLETION_REVIEW_DECISION_ROOT}/review.ndjson" node scripts/ql3-local-alpha-reconciliation-decision-fixture.cjs \ --mode=automation \ --reconciliation-root="${COMPLETION_FLOW_ROOT}" \ --output="${COMPLETION_AUTOMATION_DECISION_ROOT}/automation.ndjson" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ apply-plan edge "${COMPLETION_REHEARSAL_ROOT}" \ "${COMPLETION_CAPTURE_ROOT}" "${COMPLETION_FLOW_ROOT}" \ "${COMPLETION_AUTOMATION_DECISION_ROOT}/automation.ndjson" \ "${COMPLETION_REVIEW_DECISION_ROOT}/review.ndjson" \ "${COMPLETION_LEGACY_ROOT}" grep -q '"status":"secret_config_decision_required"' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"automaticCandidateDecision":"not_authorized"' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"state":"reconciliation_run_history_preserved"' \ "${COMPLETION_FLOW_ROOT}/results/run-history-verify.result.json" node scripts/ql3-local-alpha-reconciliation-decision-fixture.cjs \ --mode=secret-config \ --reconciliation-root="${COMPLETION_FLOW_ROOT}" \ --output="${COMPLETION_SECRET_CONFIG_DECISION_ROOT}/secret-config.ndjson" sh "${BUNDLE_ROOT}/reconciliation-rehearsal.sh" \ complete edge "${COMPLETION_REHEARSAL_ROOT}" \ "${COMPLETION_CAPTURE_ROOT}" "${COMPLETION_FLOW_ROOT}" \ "${COMPLETION_SECRET_CONFIG_DECISION_ROOT}/secret-config.ndjson" \ "${COMPLETION_REVIEW_DECISION_ROOT}/review.ndjson" \ "${COMPLETION_LEGACY_ROOT}" grep -q '"status":"reconciliation_completed"' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"adapterCount":3' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"targetRestart":"not_authorized"' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"legacyRestart":"not_authorized"' \ "${COMPLETION_FLOW_ROOT}/summary.json" grep -q '"status":"verified"' \ "${COMPLETION_FLOW_ROOT}/results/completion-verify.result.json" docker rm "${COMPLETION_TARGET_CONTAINER}" "${COMPLETION_LEGACY_CONTAINER}" test ! -e "${COMPLETION_LEGACY_ROOT}/db/database.sqlite-wal" test ! -e "${COMPLETION_LEGACY_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