diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a48d8b1..2f04339 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -227,3 +227,55 @@ jobs: with: name: inttest-results path: inttest-results.json + + beta-gate: + needs: [integration-test] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: self-hosted-gremlin + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@v5 + + - name: Install kubectl + run: | + if ! command -v kubectl &> /dev/null; then + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + chmod +x kubectl + sudo mv kubectl /usr/local/bin/kubectl + fi + kubectl version --client + + - name: Install Helm + run: | + if ! command -v helm &> /dev/null; then + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash + fi + helm version + + - name: Configure kubectl + run: | + if [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then + kubectl config set-cluster in-cluster \ + --server=https://kubernetes.default.svc \ + --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt + kubectl config set-credentials runner \ + --token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" + kubectl config set-context runner --cluster=in-cluster --user=runner + kubectl config use-context runner + fi + kubectl cluster-info || echo "WARNING: kubectl cannot reach cluster API" + + - name: Run beta gate (deploy → test → promote) + run: | + bash infra/inttest/promote.sh \ + --image-tag ${{ github.sha }} \ + --results-file beta-gate-results.json + + - name: Upload beta gate results + if: always() + uses: actions/upload-artifact@v4 + with: + name: beta-gate-results + path: beta-gate-results.json diff --git a/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz b/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz index aa18e8c..e278eb9 100644 Binary files a/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz and b/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz differ diff --git a/frontend/src/api/hooks.ts b/frontend/src/api/hooks.ts index f44a707..f1b9532 100644 --- a/frontend/src/api/hooks.ts +++ b/frontend/src/api/hooks.ts @@ -533,6 +533,14 @@ export function useRetryFailedExtractions() { }); } +export function usePipelineToggle() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (enabled: boolean) => apiPost<{ pipeline_enabled: boolean }>('query', '/api/ops/pipeline/toggle', { enabled }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['pipeline-health'] }), + }); +} + export function useIngestionSummary(hours = 24) { return useGet>(['ingestion-summary', hours], 'query', `/api/ops/ingestion/summary?hours=${hours}`); } diff --git a/frontend/src/pages/OpsPipeline.tsx b/frontend/src/pages/OpsPipeline.tsx index 10f673b..c8b4156 100644 --- a/frontend/src/pages/OpsPipeline.tsx +++ b/frontend/src/pages/OpsPipeline.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { usePipelineHealth, useRetryFailedExtractions } from '../api/hooks'; +import { usePipelineHealth, useRetryFailedExtractions, usePipelineToggle } from '../api/hooks'; import { LoadingSpinner, DateRangeSelector, Card } from '../components/ui'; const QUEUE_LABELS: Record = { @@ -54,12 +54,14 @@ export function OpsPipelinePage() { const { data, isLoading } = usePipelineHealth(hours); const stream = usePipelineStream(); const retryMutation = useRetryFailedExtractions(); + const toggleMutation = usePipelineToggle(); if (isLoading) return ; const parsing = (data?.parsing ?? {}) as Record; const extraction = (data?.extraction ?? {}) as Record; const aggregation = (data?.aggregation ?? {}) as Record; + const pipelineEnabled = (data?.pipeline_enabled ?? true) as boolean; // Prefer live stream data for queue depths and doc stages, fall back to initial fetch const queueDepths = stream?.queue_depths @@ -82,6 +84,14 @@ export function OpsPipelinePage() {

Pipeline Health

+ {failedCount > 0 && (