diff --git a/.claude/agents/backend-engineer.md b/.claude/agents/backend-engineer.md
index dbcc18dd..a4bcb030 100644
--- a/.claude/agents/backend-engineer.md
+++ b/.claude/agents/backend-engineer.md
@@ -4,7 +4,7 @@ model: sonnet
description: Implements ONLY the backend slice of a feature — HTTP API/services, business logic, DB schema/migrations, events, and the backend's own unit tests — against a frozen API contract. Does NOT build UI, the independent test suite, deploy wiring, or docs. Use for backend implementation in a contract-first fan-out.
# Figma is reserved for frontend-engineer; pure-code agent gets core tools only (no MCP).
tools: Task, Bash, Glob, Grep, LS, Read, Edit, MultiEdit, Write, NotebookEdit, WebFetch, WebSearch, TodoWrite
-skills: [api-contract-first, feature-flags, verification-protocol, model-cascade]
+skills: [api-contract-first, feature-flags, logging, verification-protocol, model-cascade]
---
You are a **backend engineer** for FuzeFront. You implement the **backend slice only**.
diff --git a/.claude/agents/frontend-engineer.md b/.claude/agents/frontend-engineer.md
index 2ccbc44a..3e8d6a02 100644
--- a/.claude/agents/frontend-engineer.md
+++ b/.claude/agents/frontend-engineer.md
@@ -5,7 +5,7 @@ description: Implements ONLY the UI slice of a feature — a design-system-first
# SOLE owner of the Figma MCP plugin (design-to-code). All other domain agents have
# Figma removed from their tool grant — it is reserved here for the UI/design-system slice.
tools: "*"
-skills: [fuzefront-ui-package, design-system-inheritance, design-system-conformance, ui-frame-contract, frontend-design, feature-flags, ui-runtime-validation, verification-protocol, model-cascade]
+skills: [fuzefront-ui-package, design-system-inheritance, design-system-conformance, ui-frame-contract, frontend-design, feature-flags, logging, ui-runtime-validation, verification-protocol, model-cascade]
---
You are a **frontend engineer**. You implement the **UI slice only**.
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index ba45be07..74c7eddd 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,586 +1,608 @@
-name: Deploy FuzeFront Website (SSH)
-
-on:
- push:
- branches: [ master, fuzefront-website ]
- pull_request:
- branches: [ master ]
-
-env:
- AWS_REGION: us-east-1
- ECR_REPOSITORY_BACKEND: fuzefront-website-backend
- ECR_REPOSITORY_FRONTEND: fuzefront-website-frontend
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
+name: Deploy FuzeFront Website (SSH)
+
+# SCOPED to the website stack. Everything this workflow touches lives under
+# fuzefront-website/ (backend, frontend, and infrastructure/main.tf), but it had
+# no `paths:` filter — so it planned Terraform against AWS on EVERY pull request
+# to master, including ones that change nothing it deploys.
+#
+# That plan currently fails outright: data.aws_vpc.default, data.aws_lb.main and
+# data.aws_autoscaling_group.main all resolve to nothing in us-east-1, i.e. the
+# AWS resources these data sources read no longer exist. So this was a
+# permanently-red check on unrelated PRs.
+#
+# The filter does NOT hide that. A PR that touches fuzefront-website/** still
+# runs it and still fails — loudly, on the change that owns the problem. What it
+# stops is an unrelated PR being blocked by infrastructure it never touched.
+#
+# Whether the AWS website stack should exist at all is a separate, open question
+# (prod is Contabo k3s via Helm/Argo per CLAUDE.md). Not decided here.
+on:
+ push:
+ branches: [ master, fuzefront-website ]
+ paths:
+ - 'fuzefront-website/**'
+ - '.github/workflows/deploy.yml'
+ pull_request:
+ branches: [ master ]
+ paths:
+ - 'fuzefront-website/**'
+ - '.github/workflows/deploy.yml'
+
+env:
+ AWS_REGION: us-east-1
+ ECR_REPOSITORY_BACKEND: fuzefront-website-backend
+ ECR_REPOSITORY_FRONTEND: fuzefront-website-frontend
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
node-version: '24.x'
- cache: 'npm'
- cache-dependency-path: |
- fuzefront-website/backend/package-lock.json
- fuzefront-website/frontend/package-lock.json
-
- - name: Install backend dependencies
- working-directory: ./fuzefront-website/backend
- run: npm ci
-
- - name: Install frontend dependencies
- working-directory: ./fuzefront-website/frontend
- run: npm ci
-
- - name: Run backend tests
- working-directory: ./fuzefront-website/backend
- run: npm test
-
- - name: Run backend linting
- working-directory: ./fuzefront-website/backend
- run: npm run lint
-
- - name: Run backend type checking
- working-directory: ./fuzefront-website/backend
- run: npm run type-check
-
- - name: Run frontend linting
- working-directory: ./fuzefront-website/frontend
- run: npm run lint
-
- - name: Run frontend type checking
- working-directory: ./fuzefront-website/frontend
- run: npm run type-check
-
- - name: Build frontend
- working-directory: ./fuzefront-website/frontend
- run: npm run build
-
- build-and-push:
- needs: test
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v6
- with:
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- aws-region: ${{ env.AWS_REGION }}
-
- - name: Login to Amazon ECR
- id: login-ecr
- uses: aws-actions/amazon-ecr-login@v2
-
- - name: Create ECR repositories if they don't exist
- run: |
- echo "🔍 Creating ECR repositories if needed..."
- aws ecr describe-repositories --repository-names $ECR_REPOSITORY_BACKEND --region $AWS_REGION || \
- aws ecr create-repository --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION
-
- aws ecr describe-repositories --repository-names $ECR_REPOSITORY_FRONTEND --region $AWS_REGION || \
- aws ecr create-repository --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION
- echo "✅ ECR repositories ready"
-
- - name: Check current ECR images (before build)
- env:
- ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
- run: |
- echo "🔍 Current ECR images before build:"
- echo "Backend images:"
- aws ecr list-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table || echo "No backend images found"
- echo "Frontend images:"
- aws ecr list-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table || echo "No frontend images found"
-
- - name: Build and push backend Docker image
- env:
- ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
- IMAGE_TAG: ${{ github.sha }}
- run: |
- echo "🚀 Building backend Docker image..."
- echo "ECR Registry: $ECR_REGISTRY"
- echo "Image Tag: $IMAGE_TAG"
- echo "Repository: $ECR_REPOSITORY_BACKEND"
-
- cd fuzefront-website/backend
- echo "📁 Working directory: $(pwd)"
- echo "📋 Files in backend directory:"
- ls -la
-
- # Clear Docker cache and system
- echo "🧹 Cleaning Docker cache..."
- docker builder prune -f || true
- docker system prune -f || true
-
- # Build backend image with comprehensive logging
- echo "🔨 Building backend image with tag: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"
- if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG .; then
- echo "✅ Backend build successful"
- else
- echo "❌ Backend build failed"
- exit 1
- fi
-
- echo "🔨 Building backend image with latest tag..."
- if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest .; then
- echo "✅ Backend latest build successful"
- else
- echo "❌ Backend latest build failed"
- exit 1
- fi
-
- # Push images with error checking
- echo "📤 Pushing backend image: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"
- if docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG; then
- echo "✅ Backend push successful"
- else
- echo "❌ Backend push failed"
- exit 1
- fi
-
- echo "📤 Pushing backend image: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest"
- if docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest; then
- echo "✅ Backend latest push successful"
- else
- echo "❌ Backend latest push failed"
- exit 1
- fi
-
- - name: Build and push frontend Docker image
- env:
- ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
- IMAGE_TAG: ${{ github.sha }}
- run: |
- echo "🚀 Building frontend Docker image..."
- echo "ECR Registry: $ECR_REGISTRY"
- echo "Image Tag: $IMAGE_TAG"
- echo "Repository: $ECR_REPOSITORY_FRONTEND"
-
- cd fuzefront-website/frontend
- echo "📁 Working directory: $(pwd)"
- echo "📋 Files in frontend directory:"
- ls -la
-
- echo "🔍 Checking App.tsx content:"
- head -10 src/App.tsx
-
- # Clear Docker cache and system
- echo "🧹 Cleaning Docker cache..."
- docker builder prune -f || true
- docker system prune -f || true
-
- # Build frontend image with comprehensive logging
- echo "🔨 Building frontend image with tag: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"
- if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG .; then
- echo "✅ Frontend build successful"
- else
- echo "❌ Frontend build failed"
- exit 1
- fi
-
- echo "🔨 Building frontend image with latest tag..."
- if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest .; then
- echo "✅ Frontend latest build successful"
- else
- echo "❌ Frontend latest build failed"
- exit 1
- fi
-
- # Push images with error checking
- echo "📤 Pushing frontend image: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"
- if docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG; then
- echo "✅ Frontend push successful"
- else
- echo "❌ Frontend push failed"
- exit 1
- fi
-
- echo "📤 Pushing frontend image: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest"
- if docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest; then
- echo "✅ Frontend latest push successful"
- else
- echo "❌ Frontend latest push failed"
- exit 1
- fi
-
- - name: Verify ECR images were pushed successfully
- env:
- ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
- IMAGE_TAG: ${{ github.sha }}
- run: |
- echo "🔍 Verifying ECR images after push:"
-
- echo "Backend images:"
- if aws ecr describe-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --image-ids imageTag=latest --query 'imageDetails[0].imagePushedAt' --output text; then
- echo "✅ Backend latest image verified in ECR"
- else
- echo "❌ Backend latest image NOT found in ECR"
- exit 1
- fi
-
- echo "Frontend images:"
- if aws ecr describe-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --image-ids imageTag=latest --query 'imageDetails[0].imagePushedAt' --output text; then
- echo "✅ Frontend latest image verified in ECR"
- else
- echo "❌ Frontend latest image NOT found in ECR"
- exit 1
- fi
-
- echo "📊 All ECR images after build:"
- echo "Backend:"
- aws ecr list-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table
- echo "Frontend:"
- aws ecr list-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table
-
- deploy-infrastructure:
- needs: build-and-push
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
- outputs:
- asg_name: ${{ steps.terraform-outputs.outputs.asg_name }}
- alb_dns_name: ${{ steps.terraform-outputs.outputs.alb_dns_name }}
- instance_id: ${{ steps.ensure-instance.outputs.instance_id }}
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v6
- with:
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- aws-region: ${{ env.AWS_REGION }}
-
- - name: Setup Terraform
- uses: hashicorp/setup-terraform@v3
- with:
- terraform_version: 1.5.0
-
- - name: Terraform Init
- working-directory: ./fuzefront-website/infrastructure
- run: terraform init
-
- - name: Terraform Plan
- working-directory: ./fuzefront-website/infrastructure
- run: |
- terraform plan \
- -var="domain_name=${{ secrets.DOMAIN_NAME }}" \
- -var="ssl_email=${{ secrets.SSL_EMAIL }}" \
- -out=tfplan
-
- - name: Terraform Apply
- working-directory: ./fuzefront-website/infrastructure
- run: terraform apply tfplan
-
- - name: Get Terraform outputs
- working-directory: ./fuzefront-website/infrastructure
- id: terraform-outputs
- run: |
- echo "alb_dns_name=$(terraform output -raw load_balancer_dns_name)" >> $GITHUB_OUTPUT
- echo "asg_name=$(terraform output -raw autoscaling_group_name)" >> $GITHUB_OUTPUT
-
- - name: Ensure dedicated deployment instance exists
- id: ensure-instance
- run: |
- echo "Checking for dedicated deployment instance..."
-
- # Check if our specific named instance exists and is running
- INSTANCE_ID=$(aws ec2 describe-instances \
- --region ${{ env.AWS_REGION }} \
- --filters \
- "Name=instance-state-name,Values=running" \
- "Name=tag:Name,Values=fuzefront-website-production-instance" \
- --query 'Reservations[0].Instances[0].InstanceId' \
- --output text 2>/dev/null || echo "None")
-
- if [ "$INSTANCE_ID" == "None" ] || [ -z "$INSTANCE_ID" ]; then
- echo "No dedicated instance found. Creating one..."
-
- # Force ASG to create an instance with the correct name
- aws autoscaling update-auto-scaling-group \
- --auto-scaling-group-name ${{ steps.terraform-outputs.outputs.asg_name }} \
- --min-size 1 \
- --desired-capacity 1 \
- --max-size 3 \
- --region ${{ env.AWS_REGION }}
-
- echo "Waiting for ASG to create instance..."
- for i in {1..20}; do
- sleep 30
- INSTANCE_ID=$(aws ec2 describe-instances \
- --region ${{ env.AWS_REGION }} \
- --filters \
- "Name=instance-state-name,Values=running" \
- "Name=tag:aws:autoscaling:groupName,Values=${{ steps.terraform-outputs.outputs.asg_name }}" \
- --query 'Reservations[0].Instances[0].InstanceId' \
- --output text 2>/dev/null || echo "None")
-
- if [ "$INSTANCE_ID" != "None" ] && [ -n "$INSTANCE_ID" ]; then
- echo "Instance created: $INSTANCE_ID"
-
- # Ensure it has the correct name tag
- aws ec2 create-tags \
- --region ${{ env.AWS_REGION }} \
- --resources $INSTANCE_ID \
- --tags Key=Name,Value=fuzefront-website-production-instance
-
- echo "Instance tagged with correct name"
- break
- fi
-
- echo "Attempt $i: No instance ready yet, waiting 30s..."
- if [ $i -eq 20 ]; then
- echo "ERROR: Failed to create instance after 10 minutes"
- exit 1
- fi
- done
- else
- echo "Dedicated instance already exists: $INSTANCE_ID"
- fi
-
- echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT
-
- deploy-application:
- needs: deploy-infrastructure
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
-
- steps:
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v6
- with:
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- aws-region: ${{ env.AWS_REGION }}
-
- - name: Setup SSH key
- run: |
- echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/ssh_key
- chmod 600 /tmp/ssh_key
-
- - name: Get target instance IP
- id: get-instances
- run: |
- # Use the instance ID from infrastructure deployment step
- INSTANCE_ID="${{ needs.deploy-infrastructure.outputs.instance_id }}"
- echo "Using dedicated instance from previous step: $INSTANCE_ID"
- echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT
-
- - name: Get instance IP address
- id: get-ip
- run: |
- INSTANCE_IP=$(aws ec2 describe-instances \
- --instance-ids ${{ steps.get-instances.outputs.instance_id }} \
- --query 'Reservations[0].Instances[0].PublicIpAddress' \
- --output text)
- echo "Instance IP: $INSTANCE_IP"
- echo "instance_ip=$INSTANCE_IP" >> $GITHUB_OUTPUT
-
- - name: Deploy to existing instance via SSH
- run: |
- INSTANCE_IP="${{ steps.get-ip.outputs.instance_ip }}"
- echo "Deploying to instance: $INSTANCE_IP"
-
- # Wait for SSH to be available
- echo "Waiting for SSH to be available..."
- for i in {1..30}; do
- SSH_OUTPUT=$(ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no -o ConnectTimeout=10 ubuntu@$INSTANCE_IP 'echo "SSH OK"' 2>&1)
- SSH_EXIT_CODE=$?
-
- if [ $SSH_EXIT_CODE -eq 0 ]; then
- echo "SSH connection successful"
- break
- fi
-
- # Check for permission denied error - don't retry these
- if echo "$SSH_OUTPUT" | grep -q "Permission denied (publickey)"; then
- echo "ERROR: SSH Permission denied (publickey) - check your SSH key configuration"
- echo "SSH Output: $SSH_OUTPUT"
- exit 1
- fi
-
- echo "SSH attempt $i failed, retrying in 10 seconds..."
- echo "SSH error: $SSH_OUTPUT"
- sleep 10
- if [ $i -eq 30 ]; then
- echo "SSH connection failed after 30 attempts"
- echo "Final SSH error: $SSH_OUTPUT"
- exit 1
- fi
- done
-
- # First check the current state of the instance
- echo "Checking current instance state..."
- ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ubuntu@$INSTANCE_IP << 'EOF'
- echo "=== Instance Status Check ==="
- echo "OS Info: $(cat /etc/os-release | grep PRETTY_NAME)"
- echo "Uptime: $(uptime)"
- echo "Docker installed: $(which docker || echo 'NOT INSTALLED')"
- echo "Docker running: $(sudo systemctl is-active docker || echo 'NOT RUNNING')"
- echo "Docker version: $(docker --version 2>/dev/null || echo 'DOCKER NOT AVAILABLE')"
- echo "Existing containers: $(docker ps -a --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null || echo 'DOCKER NOT AVAILABLE')"
- echo "Deploy script exists: $(ls -la /opt/fuzefront-website/deploy.sh 2>/dev/null || echo 'DEPLOY SCRIPT MISSING')"
- echo "SSM Agent status: $(sudo systemctl is-active amazon-ssm-agent || echo 'SSM NOT RUNNING')"
- echo "=== End Status Check ==="
- EOF
-
- # Enhanced deployment with forced container restart
- echo "🚀 Proceeding with enhanced deployment..."
- ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ubuntu@$INSTANCE_IP << 'EOF'
- export AWS_REGION=${{ env.AWS_REGION }}
- export ECR_REPOSITORY_BACKEND=${{ env.ECR_REPOSITORY_BACKEND }}
- export ECR_REPOSITORY_FRONTEND=${{ env.ECR_REPOSITORY_FRONTEND }}
-
- echo "🔍 Pre-deployment container status:"
- docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || echo "No containers running"
-
- echo "🔍 Current ECR images on instance:"
- docker images | grep fuzefront-website || echo "No fuzefront images found"
-
- echo "🔄 Forcing ECR login and image pull..."
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com
-
- echo "📥 Pulling latest images from ECR..."
- docker pull 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-backend:latest
- docker pull 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-frontend:latest
-
- echo "🛑 Stopping existing containers..."
- docker stop backend frontend reverse-proxy 2>/dev/null || echo "Some containers already stopped"
- docker rm backend frontend reverse-proxy 2>/dev/null || echo "Some containers already removed"
-
- echo "🌐 Ensuring Docker network exists..."
- docker network create fuzefront-network 2>/dev/null || echo "Network already exists"
-
- echo "🚀 Starting fresh containers with latest images..."
-
- # Start backend
- docker run -d \
- --name backend \
- --network fuzefront-network \
- --restart unless-stopped \
- -p 3001:3001 \
- 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-backend:latest
-
- # Start frontend
- docker run -d \
- --name frontend \
- --network fuzefront-network \
- --restart unless-stopped \
- -p 3000:3000 \
- 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-frontend:latest
-
- # Start reverse proxy
- docker run -d \
- --name reverse-proxy \
- --network fuzefront-network \
- --restart unless-stopped \
- -p 80:80 \
- -v /tmp/nginx-reverse-proxy/nginx.conf:/etc/nginx/nginx.conf:ro \
- nginx:alpine
-
- echo "⏳ Waiting for containers to start..."
- sleep 30
-
- echo "🔍 Post-deployment container status:"
- docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
-
- echo "🏥 Testing container health:"
- echo "Backend health:"
- curl -s http://localhost:3001/health || echo "❌ Backend health failed"
- echo ""
- echo "Frontend health:"
- curl -s http://localhost:3000/health || echo "❌ Frontend health failed"
- echo ""
- echo "Reverse proxy health:"
- curl -s http://localhost:80/health || echo "❌ Reverse proxy health failed"
-
- echo "✅ Deployment completed!"
- EOF
-
- - name: Verify deployment
- run: |
- ALB_DNS="${{ needs.deploy-infrastructure.outputs.alb_dns_name }}"
- echo "🔍 Comprehensive deployment verification"
- echo "Load Balancer DNS: $ALB_DNS"
-
- # Test health endpoint
- echo "🏥 Testing health endpoint..."
- for i in {1..30}; do
- if curl -f "http://$ALB_DNS/health"; then
- echo "✅ Health check passed!"
- break
- fi
- echo "Attempt $i failed, retrying in 10 seconds..."
- sleep 10
- done
-
- # Test analytics health endpoint (our fix)
- echo "🔍 Testing analytics health endpoint..."
- if curl -s "http://$ALB_DNS/api/analytics/health" | grep -q "healthy"; then
- echo "✅ Analytics health endpoint working!"
- else
- echo "❌ Analytics health endpoint failed"
- fi
-
- # Check for new frontend bundle (React app deployment verification)
- echo "🔍 Checking frontend bundle..."
- HTML_CONTENT=$(curl -s "http://$ALB_DNS")
- JS_BUNDLE=$(echo "$HTML_CONTENT" | grep -o '/assets/index-[^"]*\.js' | head -1)
- echo "Current JS bundle: $JS_BUNDLE"
-
- # Verify React content is being served
- echo "🔍 Verifying React content..."
- if echo "$HTML_CONTENT" | grep -q '
'; then
- echo "✅ React root element found in HTML"
- else
- echo "❌ React root element missing!"
- exit 1
- fi
-
- # Test if JS bundle is accessible
- if [ -n "$JS_BUNDLE" ]; then
- echo "🔍 Testing JS bundle accessibility..."
- if curl -f -s "http://$ALB_DNS$JS_BUNDLE" > /dev/null; then
- echo "✅ JS bundle is accessible"
-
- # Check bundle size (should be different for minimal React app)
- BUNDLE_SIZE=$(curl -s "http://$ALB_DNS$JS_BUNDLE" | wc -c)
- echo "📊 Bundle size: $BUNDLE_SIZE bytes"
-
- # Check if bundle contains our minimal React app code
- if curl -s "http://$ALB_DNS$JS_BUNDLE" | grep -q "React is Working"; then
- echo "✅ Minimal React app detected in bundle!"
- else
- echo "⚠️ Minimal React app content not found in bundle"
- fi
- else
- echo "❌ JS bundle not accessible"
- exit 1
- fi
- fi
-
- echo "🎉 Deployment verification completed!"
- echo "🌐 Website should be live at: http://$ALB_DNS"
-
- notify:
- needs: [test, build-and-push, deploy-infrastructure, deploy-application]
- runs-on: ubuntu-latest
- if: always()
-
- steps:
- - name: Notify deployment status
- if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
- run: |
- if [ "${{ needs.deploy-application.result }}" == "success" ]; then
- echo "✅ Deployment successful! Website is live at https://${{ secrets.DOMAIN_NAME }}"
- else
- echo "❌ Deployment failed. Check the logs for details."
+ cache: 'npm'
+ cache-dependency-path: |
+ fuzefront-website/backend/package-lock.json
+ fuzefront-website/frontend/package-lock.json
+
+ - name: Install backend dependencies
+ working-directory: ./fuzefront-website/backend
+ run: npm ci
+
+ - name: Install frontend dependencies
+ working-directory: ./fuzefront-website/frontend
+ run: npm ci
+
+ - name: Run backend tests
+ working-directory: ./fuzefront-website/backend
+ run: npm test
+
+ - name: Run backend linting
+ working-directory: ./fuzefront-website/backend
+ run: npm run lint
+
+ - name: Run backend type checking
+ working-directory: ./fuzefront-website/backend
+ run: npm run type-check
+
+ - name: Run frontend linting
+ working-directory: ./fuzefront-website/frontend
+ run: npm run lint
+
+ - name: Run frontend type checking
+ working-directory: ./fuzefront-website/frontend
+ run: npm run type-check
+
+ - name: Build frontend
+ working-directory: ./fuzefront-website/frontend
+ run: npm run build
+
+ build-and-push:
+ needs: test
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v6
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ env.AWS_REGION }}
+
+ - name: Login to Amazon ECR
+ id: login-ecr
+ uses: aws-actions/amazon-ecr-login@v2
+
+ - name: Create ECR repositories if they don't exist
+ run: |
+ echo "🔍 Creating ECR repositories if needed..."
+ aws ecr describe-repositories --repository-names $ECR_REPOSITORY_BACKEND --region $AWS_REGION || \
+ aws ecr create-repository --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION
+
+ aws ecr describe-repositories --repository-names $ECR_REPOSITORY_FRONTEND --region $AWS_REGION || \
+ aws ecr create-repository --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION
+ echo "✅ ECR repositories ready"
+
+ - name: Check current ECR images (before build)
+ env:
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+ run: |
+ echo "🔍 Current ECR images before build:"
+ echo "Backend images:"
+ aws ecr list-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table || echo "No backend images found"
+ echo "Frontend images:"
+ aws ecr list-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table || echo "No frontend images found"
+
+ - name: Build and push backend Docker image
+ env:
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+ IMAGE_TAG: ${{ github.sha }}
+ run: |
+ echo "🚀 Building backend Docker image..."
+ echo "ECR Registry: $ECR_REGISTRY"
+ echo "Image Tag: $IMAGE_TAG"
+ echo "Repository: $ECR_REPOSITORY_BACKEND"
+
+ cd fuzefront-website/backend
+ echo "📁 Working directory: $(pwd)"
+ echo "📋 Files in backend directory:"
+ ls -la
+
+ # Clear Docker cache and system
+ echo "🧹 Cleaning Docker cache..."
+ docker builder prune -f || true
+ docker system prune -f || true
+
+ # Build backend image with comprehensive logging
+ echo "🔨 Building backend image with tag: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"
+ if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG .; then
+ echo "✅ Backend build successful"
+ else
+ echo "❌ Backend build failed"
+ exit 1
+ fi
+
+ echo "🔨 Building backend image with latest tag..."
+ if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest .; then
+ echo "✅ Backend latest build successful"
+ else
+ echo "❌ Backend latest build failed"
+ exit 1
+ fi
+
+ # Push images with error checking
+ echo "📤 Pushing backend image: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"
+ if docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG; then
+ echo "✅ Backend push successful"
+ else
+ echo "❌ Backend push failed"
+ exit 1
+ fi
+
+ echo "📤 Pushing backend image: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest"
+ if docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:latest; then
+ echo "✅ Backend latest push successful"
+ else
+ echo "❌ Backend latest push failed"
+ exit 1
+ fi
+
+ - name: Build and push frontend Docker image
+ env:
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+ IMAGE_TAG: ${{ github.sha }}
+ run: |
+ echo "🚀 Building frontend Docker image..."
+ echo "ECR Registry: $ECR_REGISTRY"
+ echo "Image Tag: $IMAGE_TAG"
+ echo "Repository: $ECR_REPOSITORY_FRONTEND"
+
+ cd fuzefront-website/frontend
+ echo "📁 Working directory: $(pwd)"
+ echo "📋 Files in frontend directory:"
+ ls -la
+
+ echo "🔍 Checking App.tsx content:"
+ head -10 src/App.tsx
+
+ # Clear Docker cache and system
+ echo "🧹 Cleaning Docker cache..."
+ docker builder prune -f || true
+ docker system prune -f || true
+
+ # Build frontend image with comprehensive logging
+ echo "🔨 Building frontend image with tag: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"
+ if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG .; then
+ echo "✅ Frontend build successful"
+ else
+ echo "❌ Frontend build failed"
+ exit 1
+ fi
+
+ echo "🔨 Building frontend image with latest tag..."
+ if docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest .; then
+ echo "✅ Frontend latest build successful"
+ else
+ echo "❌ Frontend latest build failed"
+ exit 1
+ fi
+
+ # Push images with error checking
+ echo "📤 Pushing frontend image: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"
+ if docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG; then
+ echo "✅ Frontend push successful"
+ else
+ echo "❌ Frontend push failed"
+ exit 1
+ fi
+
+ echo "📤 Pushing frontend image: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest"
+ if docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:latest; then
+ echo "✅ Frontend latest push successful"
+ else
+ echo "❌ Frontend latest push failed"
+ exit 1
+ fi
+
+ - name: Verify ECR images were pushed successfully
+ env:
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
+ IMAGE_TAG: ${{ github.sha }}
+ run: |
+ echo "🔍 Verifying ECR images after push:"
+
+ echo "Backend images:"
+ if aws ecr describe-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --image-ids imageTag=latest --query 'imageDetails[0].imagePushedAt' --output text; then
+ echo "✅ Backend latest image verified in ECR"
+ else
+ echo "❌ Backend latest image NOT found in ECR"
+ exit 1
+ fi
+
+ echo "Frontend images:"
+ if aws ecr describe-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --image-ids imageTag=latest --query 'imageDetails[0].imagePushedAt' --output text; then
+ echo "✅ Frontend latest image verified in ECR"
+ else
+ echo "❌ Frontend latest image NOT found in ECR"
+ exit 1
+ fi
+
+ echo "📊 All ECR images after build:"
+ echo "Backend:"
+ aws ecr list-images --repository-name $ECR_REPOSITORY_BACKEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table
+ echo "Frontend:"
+ aws ecr list-images --repository-name $ECR_REPOSITORY_FRONTEND --region $AWS_REGION --query 'imageIds[*].[imageTag,imagePushedAt]' --output table
+
+ deploy-infrastructure:
+ needs: build-and-push
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
+ outputs:
+ asg_name: ${{ steps.terraform-outputs.outputs.asg_name }}
+ alb_dns_name: ${{ steps.terraform-outputs.outputs.alb_dns_name }}
+ instance_id: ${{ steps.ensure-instance.outputs.instance_id }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v6
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ env.AWS_REGION }}
+
+ - name: Setup Terraform
+ uses: hashicorp/setup-terraform@v3
+ with:
+ terraform_version: 1.5.0
+
+ - name: Terraform Init
+ working-directory: ./fuzefront-website/infrastructure
+ run: terraform init
+
+ - name: Terraform Plan
+ working-directory: ./fuzefront-website/infrastructure
+ run: |
+ terraform plan \
+ -var="domain_name=${{ secrets.DOMAIN_NAME }}" \
+ -var="ssl_email=${{ secrets.SSL_EMAIL }}" \
+ -out=tfplan
+
+ - name: Terraform Apply
+ working-directory: ./fuzefront-website/infrastructure
+ run: terraform apply tfplan
+
+ - name: Get Terraform outputs
+ working-directory: ./fuzefront-website/infrastructure
+ id: terraform-outputs
+ run: |
+ echo "alb_dns_name=$(terraform output -raw load_balancer_dns_name)" >> $GITHUB_OUTPUT
+ echo "asg_name=$(terraform output -raw autoscaling_group_name)" >> $GITHUB_OUTPUT
+
+ - name: Ensure dedicated deployment instance exists
+ id: ensure-instance
+ run: |
+ echo "Checking for dedicated deployment instance..."
+
+ # Check if our specific named instance exists and is running
+ INSTANCE_ID=$(aws ec2 describe-instances \
+ --region ${{ env.AWS_REGION }} \
+ --filters \
+ "Name=instance-state-name,Values=running" \
+ "Name=tag:Name,Values=fuzefront-website-production-instance" \
+ --query 'Reservations[0].Instances[0].InstanceId' \
+ --output text 2>/dev/null || echo "None")
+
+ if [ "$INSTANCE_ID" == "None" ] || [ -z "$INSTANCE_ID" ]; then
+ echo "No dedicated instance found. Creating one..."
+
+ # Force ASG to create an instance with the correct name
+ aws autoscaling update-auto-scaling-group \
+ --auto-scaling-group-name ${{ steps.terraform-outputs.outputs.asg_name }} \
+ --min-size 1 \
+ --desired-capacity 1 \
+ --max-size 3 \
+ --region ${{ env.AWS_REGION }}
+
+ echo "Waiting for ASG to create instance..."
+ for i in {1..20}; do
+ sleep 30
+ INSTANCE_ID=$(aws ec2 describe-instances \
+ --region ${{ env.AWS_REGION }} \
+ --filters \
+ "Name=instance-state-name,Values=running" \
+ "Name=tag:aws:autoscaling:groupName,Values=${{ steps.terraform-outputs.outputs.asg_name }}" \
+ --query 'Reservations[0].Instances[0].InstanceId' \
+ --output text 2>/dev/null || echo "None")
+
+ if [ "$INSTANCE_ID" != "None" ] && [ -n "$INSTANCE_ID" ]; then
+ echo "Instance created: $INSTANCE_ID"
+
+ # Ensure it has the correct name tag
+ aws ec2 create-tags \
+ --region ${{ env.AWS_REGION }} \
+ --resources $INSTANCE_ID \
+ --tags Key=Name,Value=fuzefront-website-production-instance
+
+ echo "Instance tagged with correct name"
+ break
+ fi
+
+ echo "Attempt $i: No instance ready yet, waiting 30s..."
+ if [ $i -eq 20 ]; then
+ echo "ERROR: Failed to create instance after 10 minutes"
+ exit 1
+ fi
+ done
+ else
+ echo "Dedicated instance already exists: $INSTANCE_ID"
+ fi
+
+ echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT
+
+ deploy-application:
+ needs: deploy-infrastructure
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
+
+ steps:
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v6
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ env.AWS_REGION }}
+
+ - name: Setup SSH key
+ run: |
+ echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/ssh_key
+ chmod 600 /tmp/ssh_key
+
+ - name: Get target instance IP
+ id: get-instances
+ run: |
+ # Use the instance ID from infrastructure deployment step
+ INSTANCE_ID="${{ needs.deploy-infrastructure.outputs.instance_id }}"
+ echo "Using dedicated instance from previous step: $INSTANCE_ID"
+ echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT
+
+ - name: Get instance IP address
+ id: get-ip
+ run: |
+ INSTANCE_IP=$(aws ec2 describe-instances \
+ --instance-ids ${{ steps.get-instances.outputs.instance_id }} \
+ --query 'Reservations[0].Instances[0].PublicIpAddress' \
+ --output text)
+ echo "Instance IP: $INSTANCE_IP"
+ echo "instance_ip=$INSTANCE_IP" >> $GITHUB_OUTPUT
+
+ - name: Deploy to existing instance via SSH
+ run: |
+ INSTANCE_IP="${{ steps.get-ip.outputs.instance_ip }}"
+ echo "Deploying to instance: $INSTANCE_IP"
+
+ # Wait for SSH to be available
+ echo "Waiting for SSH to be available..."
+ for i in {1..30}; do
+ SSH_OUTPUT=$(ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no -o ConnectTimeout=10 ubuntu@$INSTANCE_IP 'echo "SSH OK"' 2>&1)
+ SSH_EXIT_CODE=$?
+
+ if [ $SSH_EXIT_CODE -eq 0 ]; then
+ echo "SSH connection successful"
+ break
+ fi
+
+ # Check for permission denied error - don't retry these
+ if echo "$SSH_OUTPUT" | grep -q "Permission denied (publickey)"; then
+ echo "ERROR: SSH Permission denied (publickey) - check your SSH key configuration"
+ echo "SSH Output: $SSH_OUTPUT"
+ exit 1
+ fi
+
+ echo "SSH attempt $i failed, retrying in 10 seconds..."
+ echo "SSH error: $SSH_OUTPUT"
+ sleep 10
+ if [ $i -eq 30 ]; then
+ echo "SSH connection failed after 30 attempts"
+ echo "Final SSH error: $SSH_OUTPUT"
+ exit 1
+ fi
+ done
+
+ # First check the current state of the instance
+ echo "Checking current instance state..."
+ ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ubuntu@$INSTANCE_IP << 'EOF'
+ echo "=== Instance Status Check ==="
+ echo "OS Info: $(cat /etc/os-release | grep PRETTY_NAME)"
+ echo "Uptime: $(uptime)"
+ echo "Docker installed: $(which docker || echo 'NOT INSTALLED')"
+ echo "Docker running: $(sudo systemctl is-active docker || echo 'NOT RUNNING')"
+ echo "Docker version: $(docker --version 2>/dev/null || echo 'DOCKER NOT AVAILABLE')"
+ echo "Existing containers: $(docker ps -a --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null || echo 'DOCKER NOT AVAILABLE')"
+ echo "Deploy script exists: $(ls -la /opt/fuzefront-website/deploy.sh 2>/dev/null || echo 'DEPLOY SCRIPT MISSING')"
+ echo "SSM Agent status: $(sudo systemctl is-active amazon-ssm-agent || echo 'SSM NOT RUNNING')"
+ echo "=== End Status Check ==="
+ EOF
+
+ # Enhanced deployment with forced container restart
+ echo "🚀 Proceeding with enhanced deployment..."
+ ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ubuntu@$INSTANCE_IP << 'EOF'
+ export AWS_REGION=${{ env.AWS_REGION }}
+ export ECR_REPOSITORY_BACKEND=${{ env.ECR_REPOSITORY_BACKEND }}
+ export ECR_REPOSITORY_FRONTEND=${{ env.ECR_REPOSITORY_FRONTEND }}
+
+ echo "🔍 Pre-deployment container status:"
+ docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || echo "No containers running"
+
+ echo "🔍 Current ECR images on instance:"
+ docker images | grep fuzefront-website || echo "No fuzefront images found"
+
+ echo "🔄 Forcing ECR login and image pull..."
+ aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com
+
+ echo "📥 Pulling latest images from ECR..."
+ docker pull 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-backend:latest
+ docker pull 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-frontend:latest
+
+ echo "🛑 Stopping existing containers..."
+ docker stop backend frontend reverse-proxy 2>/dev/null || echo "Some containers already stopped"
+ docker rm backend frontend reverse-proxy 2>/dev/null || echo "Some containers already removed"
+
+ echo "🌐 Ensuring Docker network exists..."
+ docker network create fuzefront-network 2>/dev/null || echo "Network already exists"
+
+ echo "🚀 Starting fresh containers with latest images..."
+
+ # Start backend
+ docker run -d \
+ --name backend \
+ --network fuzefront-network \
+ --restart unless-stopped \
+ -p 3001:3001 \
+ 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-backend:latest
+
+ # Start frontend
+ docker run -d \
+ --name frontend \
+ --network fuzefront-network \
+ --restart unless-stopped \
+ -p 3000:3000 \
+ 524103092742.dkr.ecr.$AWS_REGION.amazonaws.com/fuzefront-website-frontend:latest
+
+ # Start reverse proxy
+ docker run -d \
+ --name reverse-proxy \
+ --network fuzefront-network \
+ --restart unless-stopped \
+ -p 80:80 \
+ -v /tmp/nginx-reverse-proxy/nginx.conf:/etc/nginx/nginx.conf:ro \
+ nginx:alpine
+
+ echo "⏳ Waiting for containers to start..."
+ sleep 30
+
+ echo "🔍 Post-deployment container status:"
+ docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
+
+ echo "🏥 Testing container health:"
+ echo "Backend health:"
+ curl -s http://localhost:3001/health || echo "❌ Backend health failed"
+ echo ""
+ echo "Frontend health:"
+ curl -s http://localhost:3000/health || echo "❌ Frontend health failed"
+ echo ""
+ echo "Reverse proxy health:"
+ curl -s http://localhost:80/health || echo "❌ Reverse proxy health failed"
+
+ echo "✅ Deployment completed!"
+ EOF
+
+ - name: Verify deployment
+ run: |
+ ALB_DNS="${{ needs.deploy-infrastructure.outputs.alb_dns_name }}"
+ echo "🔍 Comprehensive deployment verification"
+ echo "Load Balancer DNS: $ALB_DNS"
+
+ # Test health endpoint
+ echo "🏥 Testing health endpoint..."
+ for i in {1..30}; do
+ if curl -f "http://$ALB_DNS/health"; then
+ echo "✅ Health check passed!"
+ break
+ fi
+ echo "Attempt $i failed, retrying in 10 seconds..."
+ sleep 10
+ done
+
+ # Test analytics health endpoint (our fix)
+ echo "🔍 Testing analytics health endpoint..."
+ if curl -s "http://$ALB_DNS/api/analytics/health" | grep -q "healthy"; then
+ echo "✅ Analytics health endpoint working!"
+ else
+ echo "❌ Analytics health endpoint failed"
+ fi
+
+ # Check for new frontend bundle (React app deployment verification)
+ echo "🔍 Checking frontend bundle..."
+ HTML_CONTENT=$(curl -s "http://$ALB_DNS")
+ JS_BUNDLE=$(echo "$HTML_CONTENT" | grep -o '/assets/index-[^"]*\.js' | head -1)
+ echo "Current JS bundle: $JS_BUNDLE"
+
+ # Verify React content is being served
+ echo "🔍 Verifying React content..."
+ if echo "$HTML_CONTENT" | grep -q '
'; then
+ echo "✅ React root element found in HTML"
+ else
+ echo "❌ React root element missing!"
+ exit 1
+ fi
+
+ # Test if JS bundle is accessible
+ if [ -n "$JS_BUNDLE" ]; then
+ echo "🔍 Testing JS bundle accessibility..."
+ if curl -f -s "http://$ALB_DNS$JS_BUNDLE" > /dev/null; then
+ echo "✅ JS bundle is accessible"
+
+ # Check bundle size (should be different for minimal React app)
+ BUNDLE_SIZE=$(curl -s "http://$ALB_DNS$JS_BUNDLE" | wc -c)
+ echo "📊 Bundle size: $BUNDLE_SIZE bytes"
+
+ # Check if bundle contains our minimal React app code
+ if curl -s "http://$ALB_DNS$JS_BUNDLE" | grep -q "React is Working"; then
+ echo "✅ Minimal React app detected in bundle!"
+ else
+ echo "⚠️ Minimal React app content not found in bundle"
+ fi
+ else
+ echo "❌ JS bundle not accessible"
+ exit 1
+ fi
+ fi
+
+ echo "🎉 Deployment verification completed!"
+ echo "🌐 Website should be live at: http://$ALB_DNS"
+
+ notify:
+ needs: [test, build-and-push, deploy-infrastructure, deploy-application]
+ runs-on: ubuntu-latest
+ if: always()
+
+ steps:
+ - name: Notify deployment status
+ if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/fuzefront-website'
+ run: |
+ if [ "${{ needs.deploy-application.result }}" == "success" ]; then
+ echo "✅ Deployment successful! Website is live at https://${{ secrets.DOMAIN_NAME }}"
+ else
+ echo "❌ Deployment failed. Check the logs for details."
fi
\ No newline at end of file
diff --git a/agent-templates/schema/role-manifest.schema.json b/agent-templates/schema/role-manifest.schema.json
index 87809834..f10670fd 100644
--- a/agent-templates/schema/role-manifest.schema.json
+++ b/agent-templates/schema/role-manifest.schema.json
@@ -80,6 +80,40 @@
"metadata": {
"type": "object",
"description": "Passed through as agent `metadata` (free-form tracking)."
+ },
+ "a2a": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "OPTIONAL A2A discoverability/publication block. Mirrors the frozen contract FuzeAgent/agent-templates/contracts/a2a/v1/schema/role-a2a-extension.schema.json. Every field has a derived default, so no existing role.json needs it. The card projection reads role/name/description/services/metadata/coordinator regardless; this block only lets a role improve discoverability (examples/tags) or opt out of publication.",
+ "properties": {
+ "publish": {
+ "type": "boolean",
+ "default": true,
+ "description": "false hides this role from the public card. Still reachable on the EXTENDED card if the caller is allowlisted (authz.md §5)."
+ },
+ "extendedOnly": {
+ "type": "boolean",
+ "default": false,
+ "description": "true publishes this skill ONLY on the authenticated extended card, never on the anonymous /.well-known/agent-card.json. Use for skills whose mere existence is sensitive."
+ },
+ "tags": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Extra tags merged with the derived tags. Derived tags are never removed."
+ },
+ "examples": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Example prompts a caller can send to this skill — the primary signal a calling agent uses to decide fit. Absent examples make a skill effectively undiscoverable."
+ },
+ "inputModes": { "type": "array", "items": { "type": "string" } },
+ "outputModes": { "type": "array", "items": { "type": "string" } },
+ "scopes": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "OAuth scopes required for THIS skill, projected into the skill's securityRequirements."
+ }
+ }
}
}
}