Merge branch 'dev' into feat/epic-open-code-engine-in-keiko-2473 #4309
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: | |
| - dev | |
| - feat/dev | |
| - feat/epic-open-code-engine-in-keiko-2473 | |
| - feat/keiko-editor | |
| - feat/keiko-agent-native-editor-foundation-and-runtime | |
| - feat/prompt-enhancer-1307 | |
| - feat/keiko-establish-governed-end-to-end-git-delivery | |
| - feat/keiko-voice-digital-twin | |
| - feat/keiko-isolated-task-workspaces | |
| - feat/keiko-colleague-like-voice-dialogue-mode | |
| - feat/keiko-repository-centered-desktop-workflow | |
| - feat/epic-memoria-viva-2538 | |
| - epic/coding-workbench-opencode-codex | |
| - "release/**" | |
| pull_request: | |
| branches: | |
| - dev | |
| - feat/dev | |
| - feat/epic-open-code-engine-in-keiko-2473 | |
| - feat/keiko-editor | |
| - feat/keiko-agent-native-editor-foundation-and-runtime | |
| - feat/prompt-enhancer-1307 | |
| - feat/keiko-establish-governed-end-to-end-git-delivery | |
| - feat/keiko-voice-digital-twin | |
| - feat/keiko-isolated-task-workspaces | |
| - feat/keiko-colleague-like-voice-dialogue-mode | |
| - feat/keiko-repository-centered-desktop-workflow | |
| - feat/epic-memoria-viva-2538 | |
| - epic/coding-workbench-opencode-codex | |
| - "release/**" | |
| # ADR-0139 D7: every required workflow also validates merge-queue groups so a human can | |
| # enable GitHub's merge queue without agents re-racing a moving dev branch. | |
| merge_group: | |
| branches: | |
| - dev | |
| workflow_dispatch: | |
| permissions: {} | |
| jobs: | |
| protected-branch-gate: | |
| name: Protected branch gate | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Confirm dev branch gate | |
| run: | | |
| case "${GITHUB_REF}:${GITHUB_BASE_REF}" in | |
| refs/heads/dev: | refs/heads/feat/dev: | refs/heads/feat/epic-open-code-engine-in-keiko-2473: | refs/heads/feat/keiko-editor: | refs/heads/feat/keiko-agent-native-editor-foundation-and-runtime: | refs/heads/feat/prompt-enhancer-1307: | refs/heads/feat/keiko-establish-governed-end-to-end-git-delivery: | refs/heads/feat/keiko-voice-digital-twin: | refs/heads/feat/keiko-isolated-task-workspaces: | refs/heads/feat/keiko-colleague-like-voice-dialogue-mode: | refs/heads/feat/keiko-repository-centered-desktop-workflow: | refs/heads/feat/epic-memoria-viva-2538: | refs/heads/epic/coding-workbench-opencode-codex: | refs/heads/release/*: | *:dev | *:feat/dev | *:feat/epic-open-code-engine-in-keiko-2473 | *:feat/keiko-editor | *:feat/keiko-agent-native-editor-foundation-and-runtime | *:feat/prompt-enhancer-1307 | *:feat/keiko-establish-governed-end-to-end-git-delivery | *:feat/keiko-voice-digital-twin | *:feat/keiko-isolated-task-workspaces | *:feat/keiko-colleague-like-voice-dialogue-mode | *:feat/keiko-repository-centered-desktop-workflow | *:feat/epic-memoria-viva-2538 | *:epic/coding-workbench-opencode-codex | *:release/*) | |
| echo "Protected or integration branch gate satisfied." | |
| ;; | |
| *) | |
| echo "This gate is only valid for protected dev, integration, and release branches." | |
| exit 1 | |
| ;; | |
| esac | |
| core-quality: | |
| name: Core quality | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event_name == 'workflow_dispatch' && 'dev' || github.ref }} | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24.18.0" | |
| package-manager-cache: false | |
| - name: Verify governed Node.js and npm toolchain | |
| run: node scripts/check-runtime-toolchain.mjs --exact | |
| - run: npm ci --ignore-scripts | |
| - name: Install sandbox isolation backend (bubblewrap) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| # Ubuntu 24.04 restricts unprivileged user namespaces via AppArmor; bubblewrap (and the | |
| # unshare fallback) need them to create the network namespace. Relax it for the runner so | |
| # the enforced-egress proof below actually exercises the isolation boundary (ADR-0043). | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true | |
| - name: Enforced sandbox egress proof (ADR-0043 / #1202) | |
| # NOT gated by the feat/keiko-editor branch skip: the assured pre-filter executes untrusted | |
| # model-generated code, so the deny-by-default egress boundary must be proven on every run. | |
| # The egress test fails the job if an outbound connection from inside network:"none" succeeds. | |
| run: npm --workspace @oscharko-dev/keiko-sandbox test | |
| - name: Verify Keiko Editor package | |
| run: | | |
| npm --workspace @oscharko-dev/keiko-editor run build | |
| npm --workspace @oscharko-dev/keiko-editor run typecheck | |
| npm --workspace @oscharko-dev/keiko-editor test | |
| - run: npm run typecheck | |
| - run: npm run check:version-consistency | |
| - run: npm run check:dependency-hygiene | |
| - run: npm run check:knip | |
| - run: npm run check:qodo-config | |
| - run: npm run check:release-required-workflows | |
| - run: npm run check:portable-approvals | |
| - run: npm run check:git-client-evidence | |
| - run: npm run lint | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| - run: npm run format:check | |
| - name: UI i18n guard | |
| env: | |
| KEIKO_I18N_GUARD_BASE_REF: ${{ github.base_ref }} | |
| KEIKO_I18N_GUARD_BASE_SHA: ${{ github.event.before }} | |
| run: | | |
| if [ -n "${KEIKO_I18N_GUARD_BASE_REF:-}" ]; then | |
| git fetch --no-tags --depth=100 origin "${KEIKO_I18N_GUARD_BASE_REF}:refs/remotes/origin/${KEIKO_I18N_GUARD_BASE_REF}" | |
| elif [ "${GITHUB_EVENT_NAME:-}" = "push" ] && [ "${GITHUB_REF_NAME:-}" != "dev" ]; then | |
| git fetch --no-tags --depth=100 origin "dev:refs/remotes/origin/dev" | |
| fi | |
| npm run check:ui-i18n | |
| - run: npm run check:adr-index | |
| - run: npm run arch:check | |
| - run: npm run arch:check:negative | |
| - name: Quality Intelligence supply-chain gate | |
| run: npm run check:qi-supply-chain | |
| - name: Retrieval latency gate (enterprise retrieval M3) | |
| # Runs after `npm run typecheck` (which builds packages), so the compiled keiko-workspace | |
| # dist the gate imports is present. Deterministic fixture + generous p95 ceiling. | |
| run: npm run check:retrieval-latency | |
| - name: Retrieval quality gate (enterprise retrieval M4) | |
| # Same compiled workspace surface as the latency gate; evaluates top-k quality, line-level | |
| # evidence, and generated-artifact leakage over fixed synthetic enterprise fixtures. | |
| run: npm run check:retrieval-quality | |
| - name: Grounded retrieval quality gate (RB-5, GEN-AI-RELEASE-GATE-001) | |
| # Drives the REAL production semantic-retrieval + RRF fusion + model-reranker path (not the | |
| # lexical toy corpus) over distractor-dense fixtures with non-tautological (<1) floors, and | |
| # PROVES the gate is non-vacuous by asserting an injected ranking/reranker regression drops | |
| # below the floors. Uses the compiled keiko-server dist built by `npm run typecheck` above. | |
| run: npm run check:grounded-retrieval-quality | |
| - name: Grounded faithfulness gate (RB-4, GEN-AI-EVAL-003) | |
| # Scores the real citation-reconciliation + abstention logic over scripted answerer variants | |
| # (faithful / hallucinated-citation / confident-over-empty / refusal). Fails closed if a | |
| # fabricated citation stops being flagged or empty evidence stops abstaining. | |
| run: npm run check:grounded-faithfulness | |
| - name: Grounded entailment gate (Knowledge M1.2, Issue #2563) | |
| # Scores the real claim-segmentation + entailment-reconciliation + marker logic over | |
| # distractor-dense fixtures with a deterministic scripted judge (same port as the gateway | |
| # judge). Fails closed if an in-pack-but-unsupported claim stops being flagged, a supported | |
| # claim is falsely flagged, an unavailable judge stops degrading to WARN, or a pass-through | |
| # judge still "detects" (the checker must be load-bearing — non-tautology proven in CI). | |
| run: npm run check:grounded-entailment | |
| - name: Server error observability gate (RB-6, GEN-OBS-DIAGNOSTICS-901) | |
| # Drives the REAL built BFF: forces a handler throw and asserts the top-level catch returns an | |
| # opaque 500 that carries a correlation id (body + X-Keiko-Correlation-Id header), routes the | |
| # redacted cause to the operator diagnostic sink keyed by that id, never leaks the raw cause, | |
| # and honours a UI-supplied id. Goes RED against a bare `.catch(() => {})` that drops the id or | |
| # the diagnostic. Uses the compiled keiko-server dist built by `npm run typecheck` above. | |
| run: npm run check:error-observability | |
| - name: Context quality gate (ADR-0052, context-engineering PR1) | |
| # Runs after `npm run typecheck` (which builds packages), so the compiled keiko-contracts, | |
| # keiko-workflows, keiko-workspace, and keiko-security dist the gate imports are present — | |
| # the same build dependency the retrieval gates above rely on. Drives the deterministic lane | |
| # allocator over a fixed offline corpus and enforces context-quality invariants. | |
| run: npm run check:context-quality | |
| - name: Keiko Editor static-export bundle gate | |
| run: | | |
| npm run build:ui | |
| npm run check:editor-bundle-size -- --require-static-export | |
| npm run check:editor-release-evidence | |
| - name: Package surface and installable smoke | |
| run: | | |
| npm run build | |
| npm run prepare:bin | |
| npm run prune:package-build-artifacts | |
| npm run prune:package-native-optionals | |
| npm run check:package-surface | |
| KEIKO_SMOKE_PACK_IGNORE_SCRIPTS=1 npm run smoke:install | |
| coverage-sonar: | |
| name: Coverage and SonarCloud | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event_name == 'workflow_dispatch' && 'dev' || github.ref }} | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24.18.0" | |
| package-manager-cache: false | |
| - name: Verify governed Node.js and npm toolchain | |
| run: node scripts/check-runtime-toolchain.mjs --exact | |
| - run: npm ci --ignore-scripts | |
| - name: Install sandbox isolation backend (bubblewrap) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| # Coverage executes the same live isolation proofs as the core test job. Ubuntu 24.04 | |
| # otherwise exposes bubblewrap while AppArmor prevents it from creating the namespace. | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true | |
| - name: Coverage quality gates | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm run test:coverage:quality | |
| - name: Verify governed Sonar analysis scope | |
| run: npm run check:sonar-scope | |
| - name: Verify changed production sources are mapped into LCOV | |
| if: ${{ github.event_name == 'pull_request' && github.base_ref == 'dev' }} | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: node scripts/check-lcov-source-mapping.mjs --base "$BASE_SHA" --head "$HEAD_SHA" | |
| - name: Resolve project version for SonarCloud | |
| if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.base_ref == 'dev') || (github.event_name == 'push' && github.ref == 'refs/heads/dev') }} | |
| id: sonar-version | |
| run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" | |
| - name: Verify manual analysis is bound to remote dev | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| id: sonar-head | |
| run: | | |
| actual="$(git rev-parse HEAD)" | |
| expected="$(git rev-parse refs/remotes/origin/dev)" | |
| if [ "$actual" != "$expected" ]; then | |
| echo "Manual Sonar analysis checkout is not the fetched remote dev revision." | |
| exit 1 | |
| fi | |
| echo "sha=$actual" >> "$GITHUB_OUTPUT" | |
| - name: Download Sonar Scanner CLI | |
| # Downloaded and checksum-verified directly rather than through a marketplace action whose | |
| # license is denied by the repository dependency-review policy. | |
| if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.base_ref == 'dev') || (github.event_name == 'push' && github.ref == 'refs/heads/dev') }} | |
| run: | | |
| scanner_zip="$RUNNER_TEMP/sonar-scanner-cli.zip" | |
| curl --proto '=https' -sSfL -o "$scanner_zip" "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip" | |
| echo "bb8f709f9cb73352f8d1260a3b3c506c0f41146754bc630762c126d795499d0b $scanner_zip" | sha256sum --check | |
| unzip -q "$scanner_zip" -d "$RUNNER_TEMP" | |
| echo "SONAR_SCANNER_HOME=$RUNNER_TEMP/sonar-scanner-8.1.0.6389-linux-x64" >> "$GITHUB_ENV" | |
| - name: SonarCloud CI-based analysis (ADR-0131 / ADR-0134) | |
| if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.base_ref == 'dev') || (github.event_name == 'push' && github.ref == 'refs/heads/dev') }} | |
| id: sonar-scan | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| STEPS_SONAR_VERSION_OUTPUTS_VERSION: ${{ steps.sonar-version.outputs.version }} | |
| # Pull-request runs analyze the GitHub-provided merge ref (refs/pull/N/merge), whose commit | |
| # SHA differs from the pull-request head. Pin the analysis revision to the head commit so the | |
| # SonarCloud SCM sensor attributes changed lines to the real head instead of warning "detected | |
| # as changed but without having changed lines". Push/dispatch runs analyze their own head. | |
| SONAR_HEAD_SHA: ${{ github.event.pull_request.head.sha || steps.sonar-head.outputs.sha || github.sha }} | |
| run: | | |
| # ADR-0139 D6: transient SonarCloud-side provisioning or network failures retry with | |
| # backoff before failing closed; genuine quality-gate failures are never retried. | |
| for attempt in 1 2 3; do | |
| set +e | |
| "$SONAR_SCANNER_HOME/bin/sonar-scanner" \ | |
| -Dsonar.projectBaseDir="$GITHUB_WORKSPACE" \ | |
| -Dsonar.projectVersion="${STEPS_SONAR_VERSION_OUTPUTS_VERSION}" \ | |
| -Dsonar.scm.revision="${SONAR_HEAD_SHA}" \ | |
| -Dsonar.qualitygate.wait=true \ | |
| -Dsonar.working.directory="$RUNNER_TEMP/sonar-work" \ | |
| 2>&1 | tee "$RUNNER_TEMP/sonar-scanner.log" | |
| scanner_status=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$scanner_status" -eq 0 ]; then | |
| break | |
| fi | |
| if ! grep -qE "Gateway Timeout|Bad Gateway|Service Unavailable|Failed to query JRE metadata|connect timed out|Connection reset" "$RUNNER_TEMP/sonar-scanner.log"; then | |
| break | |
| fi | |
| if [ "$attempt" -lt 3 ]; then | |
| echo "Transient SonarCloud failure (attempt $attempt); retrying after backoff." | |
| sleep $((attempt * 30)) | |
| fi | |
| done | |
| node scripts/check-sonar-analysis-log.mjs --log "$RUNNER_TEMP/sonar-scanner.log" | |
| echo "status=$scanner_status" >> "$GITHUB_OUTPUT" | |
| if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then | |
| exit 0 | |
| fi | |
| exit "$scanner_status" | |
| - name: Verify SonarCloud Banking Grade PR evidence | |
| if: ${{ github.event_name == 'pull_request' && github.base_ref == 'dev' }} | |
| env: | |
| SONAR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| SONAR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| SONAR_PULL_REQUEST: ${{ github.event.pull_request.number }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| run: node scripts/check-sonar-pr-quality-gate.mjs | |
| - name: Verify SonarCloud Banking Grade dev evidence | |
| if: ${{ always() && github.event_name == 'workflow_dispatch' }} | |
| env: | |
| SONAR_HEAD_SHA: ${{ steps.sonar-head.outputs.sha }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| run: node scripts/check-sonar-main-quality-gate.mjs | |
| ci: | |
| name: ci | |
| if: ${{ always() }} | |
| needs: | |
| - protected-branch-gate | |
| - core-quality | |
| - coverage-sonar | |
| - cross-platform-smoke | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Aggregate required CI results fail closed | |
| env: | |
| CORE_QUALITY_RESULT: ${{ needs.core-quality.result }} | |
| COVERAGE_SONAR_RESULT: ${{ needs.coverage-sonar.result }} | |
| CROSS_PLATFORM_RESULT: ${{ needs.cross-platform-smoke.result }} | |
| PROTECTED_BRANCH_RESULT: ${{ needs.protected-branch-gate.result }} | |
| run: | | |
| for result in "$PROTECTED_BRANCH_RESULT" "$CORE_QUALITY_RESULT" "$COVERAGE_SONAR_RESULT" "$CROSS_PLATFORM_RESULT"; do | |
| if [ "$result" != "success" ]; then | |
| echo "Required CI dependency did not succeed: $result" | |
| exit 1 | |
| fi | |
| done | |
| echo "All required CI dependencies succeeded." | |
| actionlint: | |
| name: actionlint | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Download and verify actionlint | |
| run: | | |
| curl --proto '=https' -sSfL "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz" -o actionlint.tar.gz | |
| echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 actionlint.tar.gz" | sha256sum --check | |
| tar -xzf actionlint.tar.gz actionlint | |
| chmod +x actionlint | |
| - name: Run actionlint | |
| run: ./actionlint -color .github/workflows/*.yml | |
| verify-pinned-shas: | |
| name: Verify pinned action SHAs | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Assert all action references are pinned to 40-hex SHAs | |
| run: | | |
| set -euo pipefail | |
| violations=$(grep -rnE '^\s*-?\s*uses:' .github/workflows/ \ | |
| | grep -vE 'uses:\s*\./' \ | |
| | grep -vE 'uses:\s*docker://' \ | |
| | grep -vE 'uses:\s*[A-Za-z0-9._/-]+@[0-9a-f]{40}([[:space:]]|$|#)' || true) | |
| if [ -n "$violations" ]; then | |
| echo "Unpinned action references (must pin to a full 40-hex commit SHA):" | |
| echo "$violations" | |
| exit 1 | |
| fi | |
| echo "All action references are pinned to full commit SHAs." | |
| zizmor: | |
| name: zizmor | |
| if: ${{ (github.event_name == 'pull_request' && github.base_ref == 'dev') || (github.event_name == 'push' && github.ref == 'refs/heads/dev') || github.event_name == 'merge_group' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Run zizmor | |
| uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 | |
| with: | |
| version: "1.26.1" | |
| config: .github/zizmor.yml | |
| advanced-security: false | |
| annotations: true | |
| build-scan-sbom-smoke: | |
| name: Build, scan, SBOM, smoke | |
| # Keep early feature-branch PRs fast. These release/package gates still run on merges to | |
| # feat/keiko-editor, on dev/release PRs, and on manual workflow_dispatch runs. | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| attestations: write | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24.18.0" | |
| package-manager-cache: false | |
| - name: Verify governed Node.js and npm toolchain | |
| run: node scripts/check-runtime-toolchain.mjs --exact | |
| - run: npm ci --ignore-scripts | |
| - name: Build | |
| run: npm run build | |
| - name: Prepare packaged CLI and UI assets | |
| run: | | |
| npm run prepare:bin | |
| npm run build:ui | |
| npm run check:editor-release-evidence | |
| - name: Installable-package smoke (Issue #169 AC2) | |
| run: npm run smoke:install | |
| - name: Installable-package smoke with optional dependencies | |
| run: npm run smoke:install:optional | |
| - name: Installable memory smoke (Issue #216) | |
| run: npm run smoke:install:memory | |
| - name: Smoke - offline evaluate exits 0 | |
| run: KEIKO_EVIDENCE_DIR="$(mktemp -d)" node dist/cli/index.js evaluate --suite all | |
| - name: Security audit (high and above) | |
| run: npm audit --audit-level=high | |
| - name: Generate CycloneDX SBOM | |
| run: npm sbom --sbom-format cyclonedx --omit dev > sbom.cdx.json | |
| - name: Workspace SBOM and license gate (Issue #169 AC4) | |
| run: npm run check:workspace-supply-chain | |
| - name: Smoke - help exits 0 | |
| run: node dist/cli/index.js --help | |
| - name: Smoke - version exits 0 | |
| run: node dist/cli/index.js --version | |
| - name: Smoke - unknown command exits 2 | |
| run: | | |
| set +e | |
| node dist/cli/index.js definitely-not-a-command | |
| code=$? | |
| if [ "$code" -ne 2 ]; then echo "expected exit 2, got $code"; exit 1; fi | |
| echo "unknown-command exit code correct: $code" | |
| - name: Upload SBOM | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: sbom-cyclonedx | |
| path: sbom.cdx.json | |
| retention-days: 90 | |
| # Build-provenance (not SBOM-predicate) attestation: this job has no single packed artifact | |
| # to bind an SBOM-attestation subject to (no `npm pack` runs here), so this proves the SBOM | |
| # document itself was produced by this exact workflow run/commit, not tampered or fabricated. | |
| # Scoped to `push` only (dev/release branches) so PR and workflow_dispatch runs stay | |
| # unverified-staging and do not add attestation noise for commits that never ship. | |
| - name: Attest build provenance for root SBOM | |
| if: github.event_name == 'push' | |
| uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 | |
| with: | |
| subject-path: sbom.cdx.json | |
| - name: Upload workspace SBOMs (Issue #169 AC4) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: workspace-sboms-cyclonedx | |
| path: sbom/ | |
| retention-days: 90 | |
| - name: Attest build provenance for workspace SBOMs | |
| if: github.event_name == 'push' | |
| uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 | |
| with: | |
| subject-path: sbom/workspace-*.cdx.json | |
| cross-platform-smoke: | |
| name: Cross-platform smoke (${{ matrix.os }}) | |
| # The Node.js 24 baseline must execute, typecheck, build, and install on every supported desktop | |
| # OS. The complete hermetic suite remains in the Linux `ci` job because many security fixtures | |
| # intentionally model POSIX paths, Bash, and symlinks. Keep fail-fast:false so one platform | |
| # result never masks another. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24.18.0" | |
| package-manager-cache: false | |
| - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 | |
| if: runner.os == 'Windows' | |
| with: | |
| dotnet-version: "8.0.x" | |
| - name: Verify governed Node.js and npm toolchain | |
| run: node scripts/check-runtime-toolchain.mjs --exact | |
| - run: npm ci --ignore-scripts | |
| - name: Configure MSVC for native quality analysis | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio/Installer/vswhere.exe" | |
| $installation = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath | |
| if (-not $installation) { throw "Visual Studio C++ tools were not found" } | |
| $vcvars = Join-Path $installation "VC/Auxiliary/Build/vcvars64.bat" | |
| $vcvarsWrapper = Join-Path $env:RUNNER_TEMP "keiko-vcvars-env.cmd" | |
| Set-Content -LiteralPath $vcvarsWrapper -Encoding ascii -Value "@call `"$vcvars`" >nul`r`n@set" | |
| $environment = & cmd.exe /d /c $vcvarsWrapper | |
| if ($LASTEXITCODE -ne 0) { throw "MSVC environment initialization failed" } | |
| foreach ($name in @("PATH", "INCLUDE", "LIB", "LIBPATH")) { | |
| $line = $environment | Where-Object { $_ -match "^$name=" } | Select-Object -First 1 | |
| if (-not $line) { throw "MSVC environment did not define $name" } | |
| $line | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| } | |
| - name: Verify productive native sources on macOS | |
| if: runner.os == 'macOS' | |
| run: npm run check:native:macos | |
| - name: Verify productive native sources on Windows | |
| if: runner.os == 'Windows' | |
| run: npm run check:native:windows | |
| - name: Install Linux sandbox isolation backend | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 | |
| - name: Typecheck the complete package graph | |
| run: npm run typecheck | |
| - name: Build | |
| run: npm run build | |
| # Two separate steps (not one multi-line run): on the Windows runner the default shell is | |
| # PowerShell, which only surfaces the exit code of the LAST line of a multi-line `run`; as | |
| # discrete steps each command's failure fails the job on every OS. | |
| - name: Prepare packaged CLI | |
| run: npm run prepare:bin | |
| - name: Build UI (static export) | |
| run: npm run build:ui | |
| - name: Installable-package smoke (Issue #169 AC2 / #284 AC4 cross-platform) | |
| env: | |
| # Pack the dist already built by the steps above instead of re-running the Linux-oriented | |
| # `prepack` publish gate (arch-check / package-surface / supply-chain shell out to npx/npm | |
| # in ways that are not Windows-portable — a separate concern from artifact portability). | |
| # The gating `build-scan-sbom-smoke` job keeps the full prepack pack on Linux. | |
| KEIKO_SMOKE_PACK_IGNORE_SCRIPTS: "1" | |
| run: npm run smoke:install | |
| - name: Installable-package smoke with native optional dependencies | |
| env: | |
| KEIKO_SMOKE_PACK_IGNORE_SCRIPTS: "1" | |
| run: npm run smoke:install:optional | |
| ui: | |
| name: ui | |
| runs-on: ubuntu-latest | |
| permissions: | |
| attestations: write | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| # The immutable D12 evidence gate recomputes the subject for its pinned baseline commit. | |
| # Keep that commit reachable instead of relying on the default single-commit checkout. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24.18.0" | |
| package-manager-cache: false | |
| - name: Verify governed Node.js and npm toolchain | |
| run: node scripts/check-runtime-toolchain.mjs --exact | |
| - name: Install workspaces (single root lockfile after #167) | |
| run: npm ci --ignore-scripts | |
| - name: Install sandbox isolation backend (bubblewrap) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| # The #2215 real-BFF E2E executes verification commands through the same mandatory | |
| # network:none boundary as production. Ubuntu 24.04 restricts unprivileged user | |
| # namespaces via AppArmor, so enable the proven Bubblewrap path exactly as the required | |
| # ci job does; never weaken the verification runner to inherited networking for tests. | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true | |
| - name: Build internal packages so contracts dist resolves for UI tsc | |
| run: npm run build:packages | |
| - name: Security audit UI dependencies (moderate and above) | |
| run: npm audit --audit-level=moderate --workspace @oscharko-dev/keiko-ui | |
| - name: Lint UI | |
| run: npm run lint --workspace @oscharko-dev/keiko-ui | |
| - name: Typecheck UI | |
| run: npm run typecheck --workspace @oscharko-dev/keiko-ui | |
| - name: Verify UI source compatibility with TypeScript 7 | |
| run: npm run typecheck:native --workspace @oscharko-dev/keiko-ui | |
| - name: Build UI (static export) | |
| run: npm run build --workspace @oscharko-dev/keiko-ui | |
| - name: Generate UI CycloneDX SBOM | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm sbom --sbom-format cyclonedx --omit dev --workspace @oscharko-dev/keiko-ui > ui-sbom.cdx.json | |
| - name: Test UI with coverage (jsdom + axe a11y) | |
| run: npm run test:coverage:ui | |
| - name: UI coverage ratchet | |
| run: npm run check:coverage:ui | |
| - name: Install Playwright browser | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| # chromium is the reference browser for the smoke + perf-evidence suites. The | |
| # workspace-performance webkit project is a local-only cross-browser aid: headless WebKit on CI | |
| # software-renders and mis-batches events, so both its timing and write-coalescing budgets are | |
| # environment artifacts (all CI perf-evidence runs are scoped to --project=chromium). | |
| run: npx --ignore-scripts playwright@1.61.1 install --with-deps chromium | |
| - name: Release smoke E2E | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm run test:e2e:smoke | |
| - name: Editor run, verify, problems E2E (#2215) | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm run test:e2e:editor-run-verification-2215 | |
| - name: Governed editor debugging E2E (#2348) | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm run test:e2e:editor-debugging-2348 | |
| - name: Refresh workspace performance evidence | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| rm -f docs/release/1580-workspace-perf-evidence.json | |
| npm run test:e2e:workspace-perf | |
| # The editor document is the immutable D12 baseline/candidate comparison measured on the PR | |
| # candidate. Its source-tree and toolchain bindings (ADR-0139 D2: measured surfaces only) | |
| # make it valid across the signed squash and across merge-queue integration. | |
| - name: Validate immutable editor D12 performance evidence | |
| if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} | |
| run: npm run check:perf-evidence:editor | |
| - name: Performance evidence freshness + budget gate | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| run: npm run check:perf-evidence | |
| # Upload only metric documents that passed the redaction-closing schema and canonical-byte | |
| # gate above. Raw traces can contain workspace fixture text and remain outside this boundary. | |
| - name: Upload redacted performance evidence | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: performance-evidence-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: | | |
| docs/release/1209-perf-evidence.json | |
| docs/release/1580-workspace-perf-evidence.json | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| - name: Build package and UI assets | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: | | |
| npm run build | |
| npm run prepare:bin | |
| npm run build:ui | |
| npm run check:editor-release-evidence | |
| npm run prune:package-build-artifacts | |
| npm run prune:package-native-optionals | |
| - name: Verify package surface | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: npm run check:package-surface | |
| - name: Smoke - keiko ui serves /api/health | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| run: | | |
| set -euo pipefail | |
| node dist/cli/index.js ui --port 4399 & | |
| ui_pid=$! | |
| trap 'kill "$ui_pid" 2>/dev/null || true' EXIT | |
| body=$(curl --retry 10 --retry-connrefused --retry-delay 1 -fsS http://127.0.0.1:4399/api/health) | |
| echo "health: $body" | |
| echo "$body" | grep -q '"status":"ok"' | |
| - name: Upload UI SBOM | |
| if: ${{ github.event_name != 'pull_request' || github.base_ref != 'feat/keiko-editor' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ui-sbom-cyclonedx | |
| path: ui-sbom.cdx.json | |
| retention-days: 90 | |
| # Same build-provenance-of-the-document rationale as the root/workspace SBOM attestations in | |
| # the build-scan-sbom-smoke job: no single packed UI artifact exists to bind an SBOM-predicate | |
| # attestation subject to at this point, so this attests the SBOM document's own provenance. | |
| - name: Attest build provenance for UI SBOM | |
| if: github.event_name == 'push' | |
| uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 | |
| with: | |
| subject-path: ui-sbom.cdx.json |