Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/actions/setup-node-cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ inputs.node-version }}
cache: npm
Expand All @@ -46,10 +46,10 @@ runs:
npm --version
npm sbom --help > /dev/null

- name: Check lockfile drift
shell: bash
run: npm run lockfile:check

# Lockfile drift is validated once per workflow by a dedicated diagnostic gate
# (quality `lockfile-drift`, peer-matrix `lockfile`), not before every `npm ci`.
# `npm ci` already fails closed when package.json and package-lock.json diverge,
# so deterministic install is preserved without re-running the dry-run per job.
- name: Install dependencies
shell: bash
run: npm ci
Expand All @@ -67,14 +67,16 @@ runs:
# ~114 MB; downloading it every run makes the job hostage to apt mirror speed
# (a slow draw has cancelled whole shards at the job timeout). Caching the
# archives makes the download a one-time cost -- later runs install from disk.
# run_id in the key means every run refreshes the cache; restore-keys reuses
# the most recent one, so package drift only ever costs an incremental fetch.
# Key is content-derived and bounded (lockfile + Playwright config hash), not
# per-run: `github.run_id` would create an unbounded entry every run and cause
# same-run matrix save collisions. restore-keys falls back to the newest
# lane entry, so apt-version drift only ever costs an incremental fetch.
- name: Cache Playwright apt dependencies
if: ${{ inputs.install-browsers == 'true' }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/playwright-apt-debs
key: ${{ runner.os }}-pw-aptdebs-${{ inputs.cache-version }}-${{ inputs.browser-name }}-${{ github.run_id }}
key: ${{ runner.os }}-pw-aptdebs-${{ inputs.cache-version }}-${{ inputs.browser-name }}-${{ hashFiles('package-lock.json', 'configs/playwright.config.ts') }}
restore-keys: |
${{ runner.os }}-pw-aptdebs-${{ inputs.cache-version }}-${{ inputs.browser-name }}-

Expand Down
73 changes: 42 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
name: E2E Matrix

# Exhaustive daily cross-browser E2E validation feeding flakiness and SLO trends.
# Post-merge Chrome coverage is provided once by the Quality Gates `E2E (Chrome)`
# lane; this workflow runs the full browser matrix on a daily schedule (and manual
# dispatch) rather than on every main push. Trigger it manually after a risky merge
# if cross-browser confirmation is needed before the next daily run.

on:
push:
branches: [main]
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
Expand All @@ -19,43 +23,41 @@ env:

jobs:
e2e:
name: Full E2E (${{ matrix.project }} shard ${{ matrix.shard }})
name: Full E2E (${{ matrix.lane }})
runs-on: ubuntu-latest
# 30 min (was 20): webkit --with-deps can pull ~114 MB from a slow apt mirror;
# the setup action now retries+resumes that download, which needs headroom
# beyond the test run itself.
# 30 min: webkit --with-deps can pull ~114 MB from a slow apt mirror; the setup
# action retries+resumes that download, which needs headroom beyond the tests.
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 6
max-parallel: 5
# One job per installed browser binary (Playwright is already fully parallel
# across a project's tests, so browser/dependency setup -- not test time --
# dominates a job). Desktop + mobile WebKit share one webkit install in a
# single job without losing per-project results. Two-way sharding was removed:
# measured shard wall-clock was setup, not tests, so it only doubled setup.
matrix:
project:
- Google Chrome
- Firefox
- Safari
- Microsoft Edge
- Mobile Chrome
- Mobile Safari
shard: [1, 2]
include:
- project: Google Chrome
- lane: Google Chrome
install_args: chrome
project_slug: google-chrome
- project: Firefox
slug: google-chrome
projects: 'Google Chrome'
- lane: Firefox
install_args: firefox
project_slug: firefox
- project: Safari
install_args: webkit
project_slug: safari
- project: Microsoft Edge
slug: firefox
projects: 'Firefox'
- lane: Microsoft Edge
install_args: msedge
project_slug: microsoft-edge
- project: Mobile Chrome
slug: microsoft-edge
projects: 'Microsoft Edge'
- lane: Mobile Chrome
install_args: chromium
project_slug: mobile-chrome
- project: Mobile Safari
slug: mobile-chrome
projects: 'Mobile Chrome'
- lane: 'WebKit (Safari + Mobile Safari)'
install_args: webkit
project_slug: mobile-safari
slug: webkit
projects: 'Safari;Mobile Safari'
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand All @@ -73,14 +75,23 @@ jobs:

- name: Run full E2E suite
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/playwright-results-${{ matrix.project_slug }}-shard-${{ matrix.shard }}.json
run: npx playwright test --config=configs/playwright.config.ts --project="${{ matrix.project }}" --shard=${{ matrix.shard }}/2
PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/playwright-results-${{ matrix.slug }}.json
PLAYWRIGHT_PROJECTS: ${{ matrix.projects }}
# Split the semicolon-delimited project list into safely-quoted --project
# flags so multi-project lanes (WebKit) handle names with spaces correctly.
run: |
IFS=';' read -ra projects <<< "${PLAYWRIGHT_PROJECTS}"
args=()
for project in "${projects[@]}"; do
args+=(--project "${project}")
done
npx playwright test --config=configs/playwright.config.ts "${args[@]}"

- name: Upload E2E artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: e2e-matrix-artifacts-${{ matrix.project }}-shard-${{ matrix.shard }}
name: e2e-matrix-artifacts-${{ matrix.slug }}
retention-days: 14
path: |
test-results/
Expand Down
88 changes: 0 additions & 88 deletions .github/workflows/examples.yml

This file was deleted.

148 changes: 148 additions & 0 deletions .github/workflows/observability-full-stack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Observability Full Stack

# Weekly reference-stack validation of the seven-service local observability stack
# (collector, Prometheus, Grafana, Jaeger, Elasticsearch, Logstash, Kibana). This
# validates local/reference infrastructure, dashboards, provisioning, and backend
# semantics -- NOT a supported production service. It is intentionally never a PR
# gate: with only schedule + manual triggers, no runner is allocated on pull
# requests. See docs/operations/observability-support-tiers.md.

on:
schedule:
- cron: '0 6 * * 0'
workflow_dispatch:

concurrency:
group: observability-full-stack-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'

jobs:
full-stack-smoke:
name: Full Stack Smoke
runs-on: ubuntu-latest
if: vars.AURORAFLOW_OBSERVABILITY_FULL_STACK_CI_ENABLED != 'false'
timeout-minutes: 15
permissions:
contents: read
env:
OBSERVABILITY_DIAGNOSTICS_DIR: observability-output/full-stack
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Setup locked Node.js dependencies
uses: ./.github/actions/setup-node-cache
with:
node-version: '22'
cache-namespace: observability-full-stack
cache-version: v1

- name: Validate full-stack Compose config
run: docker compose -f docker-compose.yml -f docker-compose.observability.yml config

- name: Start full observability stack
run: |
mkdir -p logs test-results/self-healing "${OBSERVABILITY_DIAGNOSTICS_DIR}"
printf '{"timestamp":"%s","level":"info","message":"Observability full-stack smoke log seed.","component":"observability-ci","runId":"%s","workflow":"%s","branch":"%s","commit":"%s"}\n' \
"$(date --utc +%Y-%m-%dT%H:%M:%SZ)" \
"${GITHUB_RUN_ID}" \
"${GITHUB_WORKFLOW}" \
"${GITHUB_REF_NAME}" \
"${GITHUB_SHA}" > logs/auroraflow.ndjson
docker compose -f docker-compose.yml -f docker-compose.observability.yml up -d

- name: Wait for full-stack backends
run: |
npm run observability:validate -- \
--mode readiness \
--output-dir "${OBSERVABILITY_DIAGNOSTICS_DIR}" \
--max-attempts 60 \
--poll-interval-ms 3000 \
--timeout-ms 15000

- name: Provision Elasticsearch and Kibana assets
run: |
curl --fail --silent --show-error -X PUT http://localhost:9200/_ilm/policy/auroraflow-local-retention \
-H 'Content-Type: application/json' \
--data-binary @observability/elastic/ilm/auroraflow-local-retention.json

for template in observability/elastic/index-templates/*.json; do
name="$(basename "${template}" .json)"
curl --fail --silent --show-error -X PUT "http://localhost:9200/_index_template/${name}" \
-H 'Content-Type: application/json' \
--data-binary "@${template}"
done

curl --fail --silent --show-error -X POST http://localhost:5601/api/saved_objects/_import?overwrite=true \
-H 'kbn-xsrf: auroraflow' \
--form file=@observability/kibana/saved-objects/auroraflow-log-exploration.ndjson

- name: Emit full-stack smoke telemetry
env:
AURORAFLOW_OBSERVABILITY_DIAGNOSTICS_DIR: ${{ env.OBSERVABILITY_DIAGNOSTICS_DIR }}
AURORAFLOW_OBSERVABILITY_ENABLED: 'true'
AURORAFLOW_OBSERVABILITY_ENVIRONMENT: ci
OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318
run: npm run observability:ci:smoke

- name: Emit full-stack smoke log through Logstash
run: |
curl --fail --silent --show-error -X POST http://localhost:8080 \
-H 'Content-Type: application/json' \
--data-binary @- <<EOF
{
"timestamp": "$(date --utc +%Y-%m-%dT%H:%M:%SZ)",
"level": "info",
"message": "Observability full-stack smoke log indexed through Logstash.",
"component": "observability-ci",
"runId": "${GITHUB_RUN_ID}",
"workflow": "${GITHUB_WORKFLOW}",
"branch": "${GITHUB_REF_NAME}",
"commit": "${GITHUB_SHA}"
}
EOF

- name: Validate full-stack backend APIs
run: |
npm run observability:validate -- \
--mode smoke \
--output-dir "${OBSERVABILITY_DIAGNOSTICS_DIR}" \
--max-attempts 30 \
--poll-interval-ms 2000 \
--timeout-ms 10000

- name: Validate Prometheus labels and rules
run: npm run observability:live-assert -- --output-dir "${OBSERVABILITY_DIAGNOSTICS_DIR}"

- name: Capture backend snapshots
if: always()
run: npm run observability:snapshot -- --output-dir "${OBSERVABILITY_DIAGNOSTICS_DIR}" --allow-partial

- name: Capture full-stack service logs
if: always()
run: |
mkdir -p "${OBSERVABILITY_DIAGNOSTICS_DIR}"
docker compose -f docker-compose.yml -f docker-compose.observability.yml ps > "${OBSERVABILITY_DIAGNOSTICS_DIR}/compose-ps.txt" || true
docker compose -f docker-compose.yml -f docker-compose.observability.yml logs --no-color > "${OBSERVABILITY_DIAGNOSTICS_DIR}/compose.log" || true

- name: Stop full observability stack
if: always()
run: docker compose -f docker-compose.yml -f docker-compose.observability.yml down --remove-orphans --volumes

- name: Upload full-stack diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: observability-full-stack-diagnostics
retention-days: 7
path: |
observability-output/full-stack/
logs/auroraflow.ndjson
Loading