From bd2bebb7275af80da723642cd34518ee15c7b3d7 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Thu, 21 May 2026 13:01:54 -0700 Subject: [PATCH 1/2] fix(ci): add required PR gate --- .github/workflows/ci.yml | 104 ++++++++++++++++++++++++++++++++++- scripts/ci-workflow.spec.mjs | 72 ++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01dbb44e..5671caa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -332,7 +332,7 @@ jobs: cockpit-e2e-summary: name: "Cockpit — e2e" - needs: cockpit-e2e + needs: [ci-scope, cockpit-e2e] if: always() && (github.event_name == 'push' || needs.ci-scope.outputs.cockpit_e2e == 'true') runs-on: ubuntu-latest steps: @@ -359,6 +359,106 @@ jobs: - run: npx playwright install --with-deps chromium - run: npx nx e2e website --skip-nx-cache + required-pr-checks: + name: CI — required + needs: + - ci-scope + - library + - website + - cockpit + - cockpit-examples-build + - cockpit-smoke + - cockpit-secret-integration + - cockpit-deploy-smoke + - examples-chat-smoke + - examples-chat-e2e + - cockpit-e2e-summary + - website-e2e + - posthog-sync-plan + if: ${{ always() && github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - name: Verify scoped CI jobs + env: + RESULT_CI_SCOPE: ${{ needs.ci-scope.result }} + RESULT_LIBRARY: ${{ needs.library.result }} + RESULT_WEBSITE: ${{ needs.website.result }} + RESULT_COCKPIT: ${{ needs.cockpit.result }} + RESULT_COCKPIT_EXAMPLES: ${{ needs.cockpit-examples-build.result }} + RESULT_COCKPIT_SMOKE: ${{ needs.cockpit-smoke.result }} + RESULT_COCKPIT_SECRET: ${{ needs.cockpit-secret-integration.result }} + RESULT_COCKPIT_DEPLOY_SMOKE: ${{ needs.cockpit-deploy-smoke.result }} + RESULT_EXAMPLES_CHAT_SMOKE: ${{ needs.examples-chat-smoke.result }} + RESULT_EXAMPLES_CHAT_E2E: ${{ needs.examples-chat-e2e.result }} + RESULT_COCKPIT_E2E: ${{ needs.cockpit-e2e-summary.result }} + RESULT_WEBSITE_E2E: ${{ needs.website-e2e.result }} + RESULT_POSTHOG: ${{ needs.posthog-sync-plan.result }} + SCOPE_LIBRARY: ${{ needs.ci-scope.outputs.library }} + SCOPE_WEBSITE: ${{ needs.ci-scope.outputs.website }} + SCOPE_COCKPIT: ${{ needs.ci-scope.outputs.cockpit }} + SCOPE_COCKPIT_EXAMPLES: ${{ needs.ci-scope.outputs.cockpit_examples }} + SCOPE_COCKPIT_SMOKE: ${{ needs.ci-scope.outputs.cockpit_smoke }} + SCOPE_COCKPIT_SECRET: ${{ needs.ci-scope.outputs.cockpit_secret }} + SCOPE_COCKPIT_DEPLOY_SMOKE: ${{ needs.ci-scope.outputs.cockpit_deploy_smoke }} + SCOPE_EXAMPLES_CHAT: ${{ needs.ci-scope.outputs.examples_chat }} + SCOPE_COCKPIT_E2E: ${{ needs.ci-scope.outputs.cockpit_e2e }} + SCOPE_WEBSITE_E2E: ${{ needs.ci-scope.outputs.website_e2e }} + SCOPE_POSTHOG: ${{ needs.ci-scope.outputs.posthog }} + run: | + set -euo pipefail + + failed=0 + + require_always() { + local label="$1" + local result="$2" + + if [[ "$result" != "success" ]]; then + echo "::error::${label} finished with ${result}; refusing to report CI green." + failed=1 + fi + } + + require_scoped() { + local scope_key="$1" + local label="$2" + local result="$3" + local scoped="$4" + + if [[ "$scoped" == "true" ]]; then + if [[ "$result" != "success" ]]; then + echo "::error::${label} is required by scope ${scope_key} but finished with ${result}." + failed=1 + fi + return + fi + + if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then + echo "::error::${label} was not selected by scope ${scope_key} but finished with ${result}." + failed=1 + fi + } + + require_always "CI scope" "$RESULT_CI_SCOPE" + require_scoped "library" "Library — lint / test / build" "$RESULT_LIBRARY" "$SCOPE_LIBRARY" + require_scoped "website" "Website — lint / build" "$RESULT_WEBSITE" "$SCOPE_WEBSITE" + require_scoped "cockpit" "Cockpit — build / test" "$RESULT_COCKPIT" "$SCOPE_COCKPIT" + require_scoped "cockpit_examples" "Cockpit — build all examples" "$RESULT_COCKPIT_EXAMPLES" "$SCOPE_COCKPIT_EXAMPLES" + require_scoped "cockpit_smoke" "Cockpit — representative capability smoke" "$RESULT_COCKPIT_SMOKE" "$SCOPE_COCKPIT_SMOKE" + require_scoped "cockpit_secret" "Cockpit — secret-gated integration" "$RESULT_COCKPIT_SECRET" "$SCOPE_COCKPIT_SECRET" + require_scoped "cockpit_deploy_smoke" "Cockpit — deploy smoke dry-run" "$RESULT_COCKPIT_DEPLOY_SMOKE" "$SCOPE_COCKPIT_DEPLOY_SMOKE" + require_scoped "examples_chat" "examples/chat — python smoke" "$RESULT_EXAMPLES_CHAT_SMOKE" "$SCOPE_EXAMPLES_CHAT" + require_scoped "examples_chat" "examples/chat — e2e" "$RESULT_EXAMPLES_CHAT_E2E" "$SCOPE_EXAMPLES_CHAT" + require_scoped "cockpit_e2e" "Cockpit — e2e" "$RESULT_COCKPIT_E2E" "$SCOPE_COCKPIT_E2E" + require_scoped "website_e2e" "Website — e2e" "$RESULT_WEBSITE_E2E" "$SCOPE_WEBSITE_E2E" + require_scoped "posthog" "PostHog — dashboards-as-code drift check" "$RESULT_POSTHOG" "$SCOPE_POSTHOG" + + if [[ "$failed" -ne 0 ]]; then + exit 1 + fi + + echo "All scoped PR checks passed." + deploy: name: Deploy → Vercel needs: @@ -372,7 +472,7 @@ jobs: cockpit-deploy-smoke, examples-chat-smoke, examples-chat-e2e, - cockpit-e2e, + cockpit-e2e-summary, website-e2e, ] runs-on: ubuntu-latest diff --git a/scripts/ci-workflow.spec.mjs b/scripts/ci-workflow.spec.mjs index 5ac9ca0b..55f7eb96 100644 --- a/scripts/ci-workflow.spec.mjs +++ b/scripts/ci-workflow.spec.mjs @@ -28,6 +28,22 @@ describe('CI workflow', () => { return workflow.slice(workflow.indexOf(' posthog-sync-plan:')); } + async function readCockpitE2eSummaryJob() { + const workflow = await readWorkflow(); + return workflow.slice( + workflow.indexOf(' cockpit-e2e-summary:'), + workflow.indexOf(' website-e2e:') + ); + } + + async function readRequiredPrChecksJob() { + const workflow = await readWorkflow(); + return workflow.slice( + workflow.indexOf(' required-pr-checks:'), + workflow.indexOf(' deploy:') + ); + } + async function readPostHogQualityWorkflow() { return readFile('.github/workflows/posthog-quality.yml', 'utf8'); } @@ -136,4 +152,60 @@ describe('CI workflow', () => { ); } }); + + it('lets the cockpit e2e summary inspect CI scope outputs', async () => { + const cockpitE2eSummaryJob = await readCockpitE2eSummaryJob(); + + assert.match(cockpitE2eSummaryJob, /needs:\s*\[ci-scope,\s*cockpit-e2e\]/); + assert.match(cockpitE2eSummaryJob, /needs\.ci-scope\.outputs\.cockpit_e2e/); + }); + + it('provides one stable required PR check that waits for scoped CI jobs', async () => { + const requiredPrChecksJob = await readRequiredPrChecksJob(); + + assert.match(requiredPrChecksJob, /name:\s*CI — required/); + assert.match( + requiredPrChecksJob, + /if:\s*\$\{\{\s*always\(\)\s*&&\s*github\.event_name == 'pull_request'\s*\}\}/ + ); + + for (const job of [ + 'ci-scope', + 'library', + 'website', + 'cockpit', + 'cockpit-examples-build', + 'cockpit-smoke', + 'cockpit-secret-integration', + 'cockpit-deploy-smoke', + 'examples-chat-smoke', + 'examples-chat-e2e', + 'cockpit-e2e-summary', + 'website-e2e', + 'posthog-sync-plan', + ]) { + assert.match(requiredPrChecksJob, new RegExp(`\\b${job}\\b`)); + } + + assert.match( + requiredPrChecksJob, + /RESULT_EXAMPLES_CHAT_E2E:\s*\$\{\{\s*needs\.examples-chat-e2e\.result\s*\}\}/ + ); + assert.match( + requiredPrChecksJob, + /SCOPE_EXAMPLES_CHAT:\s*\$\{\{\s*needs\.ci-scope\.outputs\.examples_chat\s*\}\}/ + ); + assert.match( + requiredPrChecksJob, + /require_scoped "examples_chat" "examples\/chat — e2e"/ + ); + assert.match( + requiredPrChecksJob, + /require_scoped "website_e2e" "Website — e2e"/ + ); + assert.match( + requiredPrChecksJob, + /require_scoped "cockpit_e2e" "Cockpit — e2e"/ + ); + }); }); From 972306488df78c72984495465ed60183d41f73b2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 20:06:04 +0000 Subject: [PATCH 2/2] chore(docs): regenerate api docs --- apps/website/content/docs/licensing/api/api-docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/content/docs/licensing/api/api-docs.json b/apps/website/content/docs/licensing/api/api-docs.json index c72b757c..9f450918 100644 --- a/apps/website/content/docs/licensing/api/api-docs.json +++ b/apps/website/content/docs/licensing/api/api-docs.json @@ -158,7 +158,7 @@ "name": "LicenseTier", "kind": "type", "description": "The tier a license grants.", - "signature": "\"developer-seat\" | \"app-deployment\" | \"enterprise\"", + "signature": "\"indie\" | \"developer_seat\" | \"app_deployment\" | \"enterprise\"", "examples": [] }, {