diff --git a/.github/workflows/nuncio-crew-ci.yml b/.github/workflows/nuncio-crew-ci.yml index 0efedb7140f..559b0c41d27 100644 --- a/.github/workflows/nuncio-crew-ci.yml +++ b/.github/workflows/nuncio-crew-ci.yml @@ -314,6 +314,193 @@ jobs: if-no-files-found: ignore retention-days: 7 + # Advisory only (D-047 / D-032 precedent). continue-on-error keeps the signal + # visible without blocking merges. Do not add this job to `gate.needs` or + # JOB_RELEVANCE — relay flakiness and inherited Buzz integration coverage must + # not red-wall Crew PRs. Two shards match upstream wall-clock (~6–7m each with + # a warm rust cache); a single serial job would exceed ~12m once the full + # integration suite is included. Each shard builds its own buzz-relay so the + # lane stays self-contained and never becomes a hard dependency of Gate. + desktop-e2e-integration: + name: Desktop E2E Integration (${{ matrix.shard }}/2) + needs: changes + if: needs.changes.outputs.desktop == 'true' + runs-on: ubuntu-latest + timeout-minutes: 30 + continue-on-error: true + strategy: + fail-fast: false + matrix: + shard: [1, 2] + env: + BUZZ_TEST_POSTGRES_PASSWORD: buzz_dev + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - name: Restore Rust cache + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + key: nuncio-desktop-e2e-integration + save-if: ${{ github.event_name != 'pull_request' }} + - name: Start integration services + run: | + for attempt in 1 2 3; do + if docker compose up -d postgres redis minio minio-init; then + break + fi + if [ "$attempt" -eq 3 ]; then + echo "docker compose up failed after 3 attempts" >&2 + exit 1 + fi + echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 + sleep $((attempt * 5)) + done + - name: Get pnpm store directory + id: pnpm-cache + run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + - name: Restore pnpm store cache + uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: pnpm-${{ runner.os }}- + - name: Install desktop dependencies + run: just desktop-install-ci + - name: Get Playwright version + id: pw-version + run: echo "version=$(cd desktop && node -e "console.log(require('@playwright/test/package.json').version)")" >> "$GITHUB_OUTPUT" + - name: Restore Playwright browser cache + id: playwright-cache + uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} + key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} + - name: Install Playwright Chromium + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: cd desktop && pnpm exec playwright install chromium + - name: Install Playwright system dependencies + run: cd desktop && pnpm exec playwright install-deps chromium + - name: Save Playwright browser cache + if: steps.playwright-cache.outputs.cache-hit != 'true' && github.event_name == 'push' && matrix.shard == 1 + uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} + key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} + - name: Desktop E2E build + run: pnpm -C desktop build:e2e + - name: Wait for integration services + run: | + wait_healthy() { + local service="$1" + local container="$2" + for _ in $(seq 1 60); do + status=$(docker inspect --format='{{.State.Health.Status}}' "${container}" 2>/dev/null || echo "not_found") + if [ "${status}" = "healthy" ]; then + echo "${service} is healthy" + return 0 + fi + sleep 2 + done + docker logs "${container}" || true + return 1 + } + wait_healthy "Postgres" "buzz-postgres" + wait_healthy "Redis" "buzz-redis" + wait_healthy "MinIO" "buzz-minio" + - name: Build buzz-relay + run: cargo build --profile ci -p buzz-relay + - name: Apply schema and seed deployment community + # MT: the relay resolves each request's tenant from the communities host + # map and fails closed on an unmapped host. The channel reconciler binds + # the deployment community ONCE at boot (outside its retry loop) and + # exits permanently on an unmapped host, so the 'localhost:3000' + # community MUST exist before the relay starts — the retry loop only + # handles late-seeded channels, not a late-seeded community. The relay + # migrates at boot via BUZZ_AUTO_MIGRATE, but that's too late for the + # pre-boot seed, so apply the schema here first. lower(host) is the + # unique index → ON CONFLICT target. psql isn't on PATH in hermit → + # exec into the buzz-postgres container. + env: + PGHOST: localhost + PGPORT: "5432" + PGUSER: buzz + PGPASSWORD: buzz_dev + PGDATABASE: buzz + # Use the already-running docker postgres for desired-state planning + # instead of downloading an embedded Postgres from Maven Central. + PGSCHEMA_PLAN_HOST: localhost + PGSCHEMA_PLAN_PORT: "5432" + PGSCHEMA_PLAN_DB: buzz + PGSCHEMA_PLAN_USER: buzz + PGSCHEMA_PLAN_PASSWORD: buzz_dev + run: | + ./bin/pgschema apply --file schema/schema.sql --auto-approve + docker exec -i -e PGPASSWORD=buzz_dev buzz-postgres \ + psql -U buzz -d buzz -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql + docker exec -e PGPASSWORD=buzz_dev buzz-postgres \ + psql -U buzz -d buzz -qtA -c " + INSERT INTO communities (id, host) + VALUES ('00000000-0000-4000-8000-00000000c0de', 'localhost:3000') + ON CONFLICT (lower(host)) DO NOTHING + ;" + - name: Start relay + run: | + chmod +x ./target/ci/buzz-relay + nohup env \ + DATABASE_URL="postgres://buzz:${BUZZ_TEST_POSTGRES_PASSWORD}@localhost:5432/buzz" \ + REDIS_URL=redis://localhost:6379 \ + RELAY_URL=ws://localhost:3000 \ + BUZZ_BIND_ADDR=0.0.0.0:3000 \ + BUZZ_S3_ENDPOINT=http://localhost:9000 \ + BUZZ_S3_ACCESS_KEY=buzz_dev \ + BUZZ_S3_SECRET_KEY=buzz_dev_secret \ + BUZZ_S3_BUCKET=buzz-media \ + BUZZ_S3_REGION=us-east-1 \ + BUZZ_S3_ADDRESSING_STYLE=path \ + BUZZ_REQUIRE_AUTH_TOKEN=false \ + BUZZ_RECONCILE_CHANNELS=true \ + BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=100000 \ + BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=100000 \ + BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10000 \ + BUZZ_GIT_PROBE_WRITERS=8 \ + SPROUT_REMINDER_SCHEDULER_INTERVAL_SECS=1 \ + ./target/ci/buzz-relay > /tmp/buzz-relay.log 2>&1 & + echo $! > /tmp/buzz-relay.pid + for _ in $(seq 1 60); do + if ! kill -0 "$(cat /tmp/buzz-relay.pid)" 2>/dev/null; then + cat /tmp/buzz-relay.log + exit 1 + fi + status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true) + if [ "${status_code}" = "200" ]; then + exit 0 + fi + sleep 1 + done + cat /tmp/buzz-relay.log + exit 1 + - name: Seed desktop e2e data + run: bash scripts/setup-desktop-test-data.sh + - name: Desktop relay-backed e2e + run: cd desktop && pnpm exec playwright test --project=integration --shard=${{ matrix.shard }}/2 + - name: Summarize flaky tests + if: ${{ !cancelled() }} + run: node scripts/summarize-flaky-tests.mjs playwright-report.json "Desktop E2E Integration (${{ matrix.shard }}/2)" + working-directory: desktop + - name: Upload desktop integration artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: desktop-e2e-integration-artifacts-${{ matrix.shard }} + path: | + desktop/playwright-report + desktop/playwright-report.json + desktop/test-results + /tmp/buzz-relay.log + if-no-files-found: ignore + retention-days: 7 + gate: name: NuncioCrew Gate if: ${{ always() }} diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index 9ff287cb006..c938298f488 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -180,7 +180,6 @@ export default defineConfig({ "**/profile.spec.ts", "**/sidebar.spec.ts", "**/sidebar-relay-card.spec.ts", - "**/tokens.spec.ts", "**/persona-env-vars.spec.ts", "**/persona-sync.spec.ts", "**/team-snapshot.spec.ts", diff --git a/desktop/src/testing/nuncio-crew-ci-contract.test.mjs b/desktop/src/testing/nuncio-crew-ci-contract.test.mjs index d340ae20a38..8bd7554439f 100644 --- a/desktop/src/testing/nuncio-crew-ci-contract.test.mjs +++ b/desktop/src/testing/nuncio-crew-ci-contract.test.mjs @@ -131,11 +131,15 @@ test("desktop smoke e2e runs on PRs as an advisory signal until flakes are triag const ci = workflow("nuncio-crew-ci.yml"); const smokeStart = ci.indexOf("desktop-smoke-e2e:"); assert.ok(smokeStart > 0, "desktop-smoke-e2e job must exist"); + const nextJob = ci.indexOf("\n desktop-e2e-integration:", smokeStart); const gateStart = ci.indexOf("\n gate:", smokeStart); - const smoke = ci.slice( - smokeStart, - gateStart > smokeStart ? gateStart : undefined, - ); + const smokeEnd = + nextJob > smokeStart + ? nextJob + : gateStart > smokeStart + ? gateStart + : undefined; + const smoke = ci.slice(smokeStart, smokeEnd); assert.match(smoke, /name:\s*Desktop Smoke E2E/); assert.match(smoke, /continue-on-error:\s*true/); @@ -155,6 +159,41 @@ test("desktop smoke e2e runs on PRs as an advisory signal until flakes are triag assert.doesNotMatch(gateHelper, /desktop-smoke-e2e/); }); +test("desktop e2e integration runs on PRs as an advisory relay-backed lane", () => { + const ci = workflow("nuncio-crew-ci.yml"); + const integStart = ci.indexOf("desktop-e2e-integration:"); + assert.ok(integStart > 0, "desktop-e2e-integration job must exist"); + const gateStart = ci.indexOf("\n gate:", integStart); + const integ = ci.slice( + integStart, + gateStart > integStart ? gateStart : undefined, + ); + + assert.match(integ, /name:\s*Desktop E2E Integration/); + assert.match(integ, /continue-on-error:\s*true/); + assert.match(integ, /shard:\s*\[1,\s*2\]/); + assert.match(integ, /docker compose up -d postgres redis minio minio-init/); + assert.match(integ, /cargo build --profile ci -p buzz-relay/); + assert.match(integ, /BUZZ_RECONCILE_CHANNELS=true/); + assert.match(integ, /setup-desktop-test-data\.sh/); + assert.match(integ, /pnpm -C desktop build:e2e/); + assert.match( + integ, + /playwright test --project=integration --shard=\$\{\{ matrix\.shard \}\}\/2/, + ); + assert.match(integ, /needs\.changes\.outputs\.desktop == 'true'/); + // Advisory (D-047): must not be registered in the merge gate. + assert.doesNotMatch(ci, /needs\.desktop-e2e-integration\.result/); + const gateHelper = readFileSync(gateHelperPath, "utf8"); + assert.doesNotMatch(gateHelper, /desktop-e2e-integration/); + // Gate needs list must stay free of the integration job id. + const gateNeeds = ci.match( + /name:\s*NuncioCrew Gate[\s\S]*?needs:\s*\[([^\]]+)\]/, + ); + assert.ok(gateNeeds, "gate needs list must be present"); + assert.doesNotMatch(gateNeeds[1], /desktop-e2e-integration/); +}); + test("upstream compatibility is explicit and manual", () => { const upstream = workflow("nuncio-crew-upstream-sync.yml"); const trigger = upstream.slice( diff --git a/docs/crew/CI.md b/docs/crew/CI.md index 00daec1e997..935b54196ce 100644 --- a/docs/crew/CI.md +++ b/docs/crew/CI.md @@ -12,11 +12,13 @@ The gate always appears. It requires `CI Policy` and accepts a deliberately skipped conditional job only when the path classifier says that surface is unchanged. -A green `NuncioCrew Gate` is not evidence that the Desktop Smoke E2E suite -passed. +A green `NuncioCrew Gate` is not evidence that the Desktop Smoke E2E suite or +the Desktop E2E Integration suite passed. -This advisory posture is recorded in [`verification/0007`](verification/0007-gate-e2e-shard-relationship.md) -and D-032; revisit making the shards required once #109 and #110 are closed. +Smoke advisory posture is recorded in +[`verification/0007`](verification/0007-gate-e2e-shard-relationship.md) and +D-032. Integration advisory posture is recorded in D-047 (#147); revisit making +either lane required only by an explicit founder decision. | Job | Runs when | Proves | | --- | --- | --- | @@ -27,6 +29,7 @@ and D-032; revisit making the shards required once #109 and #110 are closed. | `macOS ARM Package` | Same desktop boundary as Desktop Fast | Unsigned `aarch64-apple-darwin` Tauri package with Nuncio identity | | `Project Relay` | Project, relay, schema, or Nostr paths change | Kind `30617` local-path lifecycle against an isolated real relay | | `Desktop Smoke E2E` | Desktop paths change | **nothing that blocks merge** — advisory (`continue-on-error`), excluded from the gate by design (#36/#37) | +| `Desktop E2E Integration` | Same desktop boundary as Desktop Smoke E2E | **nothing that blocks merge** — advisory (`continue-on-error`), two shards, real relay + Postgres/Redis/MinIO, `playwright --project=integration` (D-047 / #147). Includes the Crew-owned `evidence-reactions-relay` proof and inherited Buzz relay-backed specs | The PR package uses placeholder sidecars only to satisfy Tauri's packaging shape. The manual release workflow builds real sidecars, signs the app, diff --git a/docs/crew/DECISIONS.md b/docs/crew/DECISIONS.md index 8832f58aa36..5b743f46d1f 100644 --- a/docs/crew/DECISIONS.md +++ b/docs/crew/DECISIONS.md @@ -839,3 +839,34 @@ classifications. This decision changes no reaction semantics, event kinds, or evidence tag schema. D-036 remains authoritative for evidence tags and reaction behavior. + +## D-047 — Keep Desktop E2E Integration advisory until Crew owns more of the lane + +- **Status:** Accepted +- **Date:** 2026-08-12 +- **Issue:** #147 +- **Precedent:** D-032 (Desktop Smoke E2E advisory) + +The founder decided that the relay-backed Desktop E2E Integration lane +(`playwright test --project=integration` in `nuncio-crew-ci.yml`) is **advisory**: +`continue-on-error: true`, excluded from `NuncioCrew Gate` `needs`, and absent +from `JOB_RELEVANCE` in `check-nuncio-crew-ci-results.mjs`. A green Gate is not +evidence that integration passed. + +Rationale matches D-032's trade-off, applied to a heavier lane: + +1. Only one configured integration spec is Crew-specific today + (`evidence-reactions-relay.spec.ts` from #133 / PR #146); the rest are + inherited Buzz coverage that Crew deliberately stopped requiring when the + upstream `CI` workflow was disabled. +2. The lane needs Postgres, Redis, MinIO, a built `buzz-relay`, schema and + community seed, `scripts/setup-desktop-test-data.sh`, Playwright, and + `BUZZ_RECONCILE_CHANNELS=true`. Relay and service flakiness must not block + every desktop merge. +3. Restoring the lane as advisory recovers continuous signal (including the + D-042 headless click → real kind-7 proof) without red-walling merges. + +Promotion path: making the lane required is an **explicit future founder +decision**, not an automatic follow-up when a single spec turns green. Revisit +once Crew owns a meaningful share of the integration suite and the lane's +failure rate on `main` is attributed and stable enough to gate on. diff --git a/docs/crew/STATE.md b/docs/crew/STATE.md index a75d00d2137..d60dcf37d88 100644 --- a/docs/crew/STATE.md +++ b/docs/crew/STATE.md @@ -134,6 +134,10 @@ Out of scope for this slice: baseline bump while D-022 governs Crew-authored growth. - Automatic checks: desktop fast gate, unsigned macOS ARM64 package, and a path-filtered real-relay Project contract. +- Advisory (non-blocking) on desktop path changes: Desktop Smoke E2E (D-032) + and Desktop E2E Integration — real relay + Postgres/Redis/MinIO, + `playwright --project=integration`, two shards (D-047 / #147). A green Gate + is not evidence either advisory lane passed. - Web, mobile, Windows, Linux distribution, Docker publishing, Helm, Sprig, and optional mesh-llm builds are outside automatic Crew CI. - Core root and desktop Tauri Rust format, lint, unit, and dependency-policy diff --git a/docs/crew/verification/0011-e2e-bridge-relay-mutation-audit.md b/docs/crew/verification/0011-e2e-bridge-relay-mutation-audit.md index 03c0e70183e..3bdce80eab5 100644 --- a/docs/crew/verification/0011-e2e-bridge-relay-mutation-audit.md +++ b/docs/crew/verification/0011-e2e-bridge-relay-mutation-audit.md @@ -29,21 +29,23 @@ agent ownership metadata remains a bridge-config profile injection; see ## CI coverage -The new relay-backed desktop spec has **no CI coverage today**. The -`integration` project is an upstream-owned lane from `block/buzz`'s -`.github/workflows/ci.yml`, introduced by upstream commit +The relay-backed desktop spec is covered by the advisory +`Desktop E2E Integration` job in the active +`.github/workflows/nuncio-crew-ci.yml` workflow (`playwright test +--project=integration`, two shards, real Postgres/Redis/MinIO + built +`buzz-relay`, schema/community seed, `scripts/setup-desktop-test-data.sh`, +`BUZZ_RECONCILE_CHANNELS=true`). Status is **advisory** (`continue-on-error`, +excluded from `NuncioCrew Gate`) per D-047 / issue +[#147](https://github.com/Nuncio-hq/crew/issues/147); a green Gate is not +evidence that integration passed. See [CI.md](../CI.md). + +History: the `integration` project was an upstream-owned lane from +`block/buzz`'s `.github/workflows/ci.yml`, introduced by upstream commit [`a1c28f487d`](https://github.com/block/buzz/commit/a1c28f487d2af01d620619d940cf377d21c1a81a), -“Shard desktop Playwright CI jobs (#992)”. Upstream's workflow is still -`active`; Crew disabled its inherited `CI` workflow deliberately as part of -the minimised merge gate. See [CI.md](../CI.md), which records that “Upstream -Sync does not run the inherited integration or cross-platform matrices” and -the cutover step to “disable inherited `CI` and `Docker image` in GitHub -Actions”. - -In Crew, workflow `CI` is `disabled_manually` (id `323540365`), while the -active `.github/workflows/nuncio-crew-ci.yml` runs the smoke project only. -For PR #146, head `70bfa70e7`, desktop filters were true but no integration -job appeared in +“Shard desktop Playwright CI jobs (#992)”. Crew disabled its inherited `CI` +workflow deliberately as part of the minimised merge gate; before #147 the +active workflow ran smoke only. For PR #146, head `70bfa70e7`, desktop filters +were true but no integration job appeared in [31448955605](https://github.com/Nuncio-hq/crew/actions/runs/31448955605). The clean-main push run [31362178966](https://github.com/Nuncio-hq/crew/actions/runs/31362178966) @@ -51,26 +53,16 @@ also had no integration job. The lane last appeared under the disabled workflow at [30534161705/job/90843624305](https://github.com/Nuncio-hq/crew/actions/runs/30534161705/job/90843624305). -The last completed integration run, +The last completed upstream-style integration run before the Crew lane, [30533242569](https://github.com/Nuncio-hq/crew/actions/runs/30533242569), -used two `ubuntu-latest` shards. Shard 1 took about 6m45s -([job 90840519406](https://github.com/Nuncio-hq/crew/actions/runs/30533242569/job/90840519406)); -shard 2 took about 6m17s -([job 90840519509](https://github.com/Nuncio-hq/crew/actions/runs/30533242569/job/90840519509)). -Each shard has a 20-minute timeout. The job requires Postgres, Redis, MinIO -and `minio-init`, a built `buzz-relay`, schema and community setup, -`scripts/setup-desktop-test-data.sh`, Playwright installation, and -`BUZZ_RECONCILE_CHANNELS=true`. - -The active `Project Relay` job provisions its own relay stack inside -`scripts/run-nuncio-crew-project-relay-ci.sh`, but jobs do not share -services; that job neither seeds desktop E2E data nor runs Playwright. -Adding this lane would therefore duplicate the per-job relay, database, -schema, seed, browser, and E2E-build setup. It stays with -[#147](https://github.com/Nuncio-hq/crew/issues/147). Of the 17 configured -integration specs, `evidence-reactions-relay.spec.ts` is the only -Crew-specific one; the rest are inherited Buzz coverage. Every relay-backed -desktop spec is therefore locally verified only. +used two `ubuntu-latest` shards (~6m45s / ~6m17s, 20-minute per-shard +timeout). The Crew lane mirrors that shape (30-minute timeout to absorb the +per-shard relay build). `Project Relay` still provisions its own stack inside +`scripts/run-nuncio-crew-project-relay-ci.sh` and does not run Playwright; the +integration job duplicates services by design so it never becomes a hard Gate +dependency. Of the configured integration specs, +`evidence-reactions-relay.spec.ts` remains the only Crew-specific one; the +rest are inherited Buzz coverage. ### How to run this spec locally