home / skills / willsigmon / sigstack / knack-devops

knack-devops skill

/archive/knack-devops

This skill automates build, deployment, environment synchronization, and monitoring for the Knack-Vercel integration to ensure uptime and data reliability.

npx playbooks add skill willsigmon/sigstack --skill knack-devops

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
12.6 KB
---
name: Knack DevOps
description: Manages automated build, deployment, environment sync, and monitoring for HTI's Knack-Vercel integration. Ensures dashboard uptime, data sync relia...
allowed-tools: Read
---

# Knack DevOps

## Purpose
Manages automated build, deployment, environment sync, and monitoring for HTI's Knack-Vercel integration. Ensures dashboard uptime, data sync reliability, and performance optimization.

## Core Functions

### trigger_vercel_build
**Purpose**: Programmatically deploy updated dashboard code

**Parameters**:
- `vercel_url` (string, optional): Deployment URL (default: production)
- `trigger` (string, optional): "push" | "webhook" | "api" (default: "api")

**Example**:
```javascript
const deployment = await trigger_vercel_build({
  vercel_url: process.env.VERCEL_PROJECT_URL,
  trigger: "api"
});

// Output:
// {
//   deployment_id: "dpl_abc123",
//   status: "building",
//   url: "https://hubdash-git-main.vercel.app",
//   estimated_completion: "2025-04-15T14:35:00Z"
// }
```

**Implementation**:
```javascript
import { Vercel } from '@vercel/sdk';

async function trigger_vercel_build({ vercel_url }) {
  const vercel = new Vercel({ bearerToken: process.env.VERCEL_TOKEN });

  const deployment = await vercel.deployments.create({
    name: 'hubdash',
    gitSource: {
      type: 'github',
      ref: 'main'
    }
  });

  return {
    deployment_id: deployment.id,
    status: deployment.status,
    url: deployment.url
  };
}
```

### monitor_latency
**Purpose**: Track API and dashboard response times

**Parameters**:
- `api_url` (string, required): Endpoint to monitor
- `interval_seconds` (integer, optional): Check frequency (default: 300 = 5 min)
- `alert_threshold_ms` (integer, optional): Latency threshold for alerts (default: 2000)

**Example**:
```javascript
const monitor = await monitor_latency({
  api_url: "https://hubdash.vercel.app/api/metrics",
  interval_seconds: 300,
  alert_threshold_ms: 2000
});
```

**Implementation**:
```javascript
import axios from 'axios';

async function monitor_latency({ api_url, interval_seconds, alert_threshold_ms }) {
  setInterval(async () => {
    const start = Date.now();

    try {
      await axios.get(api_url);
      const latency = Date.now() - start;

      console.log(`Latency: ${latency}ms`);

      if (latency > alert_threshold_ms) {
        await alert_team({
          severity: "warning",
          message: `Dashboard latency high: ${latency}ms (threshold: ${alert_threshold_ms}ms)`
        });
      }

      // Log to metrics system
      await log_metric({
        metric: "api_latency",
        value: latency,
        timestamp: new Date()
      });
    } catch (error) {
      await alert_team({
        severity: "critical",
        message: `Dashboard unreachable: ${api_url}`,
        error: error.message
      });
    }
  }, interval_seconds * 1000);
}
```

### sync_environment
**Purpose**: Keep Knack credentials and config in sync across environments

**Environments**:
- **Development**: Local testing with sandbox Knack instance
- **Staging**: Pre-production testing with production data copy
- **Production**: Live HTI dashboard with real grant data

**Example**:
```javascript
await sync_environment({
  source: "production",
  target: "staging",
  include: ["knack_credentials", "api_endpoints"],
  exclude: ["user_tokens", "sensitive_keys"]
});
```

**Implementation**:
```javascript
async function sync_environment({ source, target, include, exclude }) {
  // Fetch config from source
  const source_config = await get_env_config(source);

  // Filter based on include/exclude
  const filtered_config = Object.keys(source_config)
    .filter(key => include.includes(key) && !exclude.includes(key))
    .reduce((obj, key) => {
      obj[key] = source_config[key];
      return obj;
    }, {});

  // Apply to target environment
  await update_env_config(target, filtered_config);

  return {
    synced_keys: Object.keys(filtered_config),
    source: source,
    target: target
  };
}
```

### automated_backup
**Purpose**: Regular backups of Knack data for disaster recovery

**Example**:
```javascript
// Daily backup of all Knack objects
cron.schedule('0 2 * * *', async () => {
  const backup = await automated_backup({
    objects: ["object_1", "object_2", "object_training"],
    destination: "s3://hti-backups/knack",
    retention_days: 90
  });

  console.log(`Backup complete: ${backup.records_backed_up} records`);
});
```

**Implementation**:
```javascript
import AWS from 'aws-sdk';

async function automated_backup({ objects, destination, retention_days }) {
  const s3 = new AWS.S3();
  const timestamp = new Date().toISOString().split('T')[0];
  let total_records = 0;

  for (const object_key of objects) {
    // Fetch all records
    const records = await fetch_all_pages(object_key);
    total_records += records.length;

    // Save to S3
    await s3.putObject({
      Bucket: 'hti-backups',
      Key: `knack/${object_key}_${timestamp}.json`,
      Body: JSON.stringify(records, null, 2),
      ContentType: 'application/json'
    }).promise();
  }

  // Clean up old backups (>90 days)
  await cleanup_old_backups({ bucket: 'hti-backups', retention_days });

  return { records_backed_up: total_records, timestamp };
}
```

## CI/CD Pipeline

### GitHub Actions Workflow
```yaml
# .github/workflows/deploy.yml
name: Deploy HTI Dashboard

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Validate Knack connection
        run: npm run test:knack-api
        env:
          KNACK_APP_ID: ${{ secrets.KNACK_APP_ID }}
          KNACK_API_KEY: ${{ secrets.KNACK_API_KEY }}

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

      - name: Notify deployment
        run: |
          curl -X POST https://hooks.slack.com/services/${{ secrets.SLACK_WEBHOOK }} \
          -d '{"text": "✅ HTI Dashboard deployed to production"}'
```

### Pre-Deployment Checks
```javascript
async function pre_deployment_checks() {
  const checks = {
    knack_api: await test_knack_connection(),
    data_integrity: await validate_production_data(),
    cache_warm: await warm_cache(),
    env_vars: await verify_env_vars(),
    build_size: await check_bundle_size()
  };

  const all_passed = Object.values(checks).every(c => c.passed);

  if (!all_passed) {
    throw new Error(`Pre-deployment checks failed: ${JSON.stringify(checks)}`);
  }

  return checks;
}

// Run before every production deploy
async function test_knack_connection() {
  try {
    const response = await get_records("object_1", { rows_per_page: 1 });
    return { passed: true, message: "Knack API accessible" };
  } catch (error) {
    return { passed: false, message: `Knack API error: ${error.message}` };
  }
}
```

## Monitoring & Alerting

### Uptime Monitoring
```javascript
// Use Vercel Analytics or external service (Pingdom, UptimeRobot)
async function setup_uptime_monitoring() {
  // Vercel Analytics (automatic)
  // https://vercel.com/docs/analytics

  // Or external monitoring
  const monitor = await createUptimeCheck({
    url: "https://hubdash.vercel.app",
    interval: 300, // 5 minutes
    alert_contacts: ["[email protected]"]
  });

  return monitor;
}
```

### Error Tracking
```javascript
// Sentry integration
import * as Sentry from "@sentry/nextjs";

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,

  beforeSend(event) {
    // Don't send test data errors
    if (event.request?.url?.includes('localhost')) {
      return null;
    }
    return event;
  }
});

// Log Knack API errors
try {
  await get_records("object_1");
} catch (error) {
  Sentry.captureException(error, {
    tags: { service: "knack_api" },
    extra: { object_key: "object_1" }
  });
}
```

### Performance Monitoring
```javascript
// Web Vitals tracking
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function send_to_analytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    id: metric.id
  });

  fetch('/api/analytics', { method: 'POST', body });
}

getCLS(send_to_analytics);
getFID(send_to_analytics);
getFCP(send_to_analytics);
getLCP(send_to_analytics);
getTTFB(send_to_analytics);
```

## Environment Management

### Development Environment
```bash
# .env.development
KNACK_APP_ID=dev_app_id
KNACK_API_KEY=dev_api_key
VERCEL_ENV=development
CACHE_TTL=30 # Shorter cache for testing
DEBUG_MODE=true
```

### Production Environment
```bash
# .env.production (in Vercel dashboard)
KNACK_APP_ID=prod_app_id
KNACK_API_KEY=prod_api_key
VERCEL_ENV=production
CACHE_TTL=300 # 5 minutes
DEBUG_MODE=false
SENTRY_DSN=https://...
```

### Secrets Management
```javascript
// Store secrets in Vercel Environment Variables
// Never commit secrets to Git

// Access in code:
const knack_api_key = process.env.KNACK_API_KEY;

// Rotate secrets quarterly (align with grant reporting)
async function rotate_secrets() {
  // 1. Generate new API key in Knack dashboard
  // 2. Update Vercel environment variables
  // 3. Trigger redeployment
  // 4. Verify new keys work
  // 5. Revoke old keys
}
```

## Performance Optimization

### Bundle Size Analysis
```javascript
// Run during CI/CD
import { readFileSync } from 'fs';

async function check_bundle_size() {
  const bundle_stats = JSON.parse(readFileSync('.next/analyze/client.json'));

  const total_size = bundle_stats.reduce((sum, bundle) => sum + bundle.size, 0);
  const max_size = 1_000_000; // 1MB

  if (total_size > max_size) {
    console.warn(`Bundle size ${total_size} exceeds ${max_size}`);
    return { passed: false, size: total_size };
  }

  return { passed: true, size: total_size };
}
```

### Cache Warming
```javascript
// Warm cache after deployment
async function warm_cache() {
  const critical_endpoints = [
    "/api/metrics",
    "/api/county-breakdown",
    "/api/quarterly-report"
  ];

  for (const endpoint of critical_endpoints) {
    await fetch(`https://hubdash.vercel.app${endpoint}`);
  }

  return { warmed: critical_endpoints.length };
}
```

## Disaster Recovery

### Database Recovery
```javascript
async function restore_from_backup({ backup_date, objects }) {
  const s3 = new AWS.S3();

  for (const object_key of objects) {
    // Fetch backup
    const backup = await s3.getObject({
      Bucket: 'hti-backups',
      Key: `knack/${object_key}_${backup_date}.json`
    }).promise();

    const records = JSON.parse(backup.Body.toString());

    // Restore to Knack (batch insert)
    console.log(`Restoring ${records.length} records to ${object_key}`);
    // Implementation depends on Knack's batch API
  }
}
```

### Rollback Procedure
```javascript
// Rollback to previous Vercel deployment
async function rollback_deployment() {
  const vercel = new Vercel({ bearerToken: process.env.VERCEL_TOKEN });

  // Get previous deployment
  const deployments = await vercel.deployments.list({
    projectId: process.env.VERCEL_PROJECT_ID,
    limit: 2
  });

  const previous = deployments[1];

  // Promote to production
  await vercel.deployments.update(previous.id, {
    target: 'production'
  });

  console.log(`Rolled back to ${previous.id}`);
}
```

## Integration Points

- **knack_reader**: Monitor API health
- **knack_cache_optimizer**: Cache invalidation on deploy
- **knack_realtime**: Webhook endpoint health checks
- **knack_reporting_sync**: Automated report generation triggers
- **knack_dashboard_ai**: Performance metrics collection

## Best Practices

1. **Automate everything**: No manual deployments
2. **Test before deploy**: CI/CD checks gate production
3. **Monitor continuously**: Uptime + performance + errors
4. **Backup daily**: 90-day retention
5. **Document runbooks**: Incident response procedures
6. **Rotate secrets quarterly**: Align with grant reporting cycle

## Incident Response

### Runbook: Dashboard Down
1. Check Vercel status page
2. Verify Knack API connectivity
3. Review recent deployments
4. Check error logs in Sentry
5. If needed, rollback deployment
6. Notify stakeholders
7. Post-incident review

### Runbook: Slow Dashboard
1. Check API latency metrics
2. Review cache hit rates
3. Analyze bundle size
4. Check Knack rate limits
5. Optimize queries if needed
6. Consider CDN configuration

Overview

This skill manages automated build, deployment, environment sync, backups, and monitoring for HTI's Knack-Vercel integration. It keeps dashboards available, data synchronized across environments, and performance observable. It combines CI/CD, uptime and latency monitoring, scheduled backups, and runbooks to reduce downtime and operational toil.

How this skill works

It triggers Vercel deployments, runs pre-deployment checks, and promotes or rolls back deployments as needed. It continuously monitors API and dashboard latency, logs performance and errors to observability tools, and alerts the team on thresholds. It also syncs environment configuration and credentials between development, staging, and production, and performs automated backups to S3 with retention and recovery tooling.

When to use it

  • Automate dashboard deployments to Vercel from CI/CD
  • Detect and alert on API or page latency spikes
  • Sync credentials and configuration across dev/staging/production
  • Run daily backups for disaster recovery with retention
  • Perform pre-deploy validation and rollback after faulty deploys
  • Warm caches and run bundle size checks before production release

Best practices

  • Automate all deployments via CI/CD and block direct production changes
  • Run full pre-deployment checks: Knack connectivity, data integrity, env vars, cache warm
  • Store secrets only in environment variable stores (Vercel), rotate quarterly
  • Monitor uptime, latency, and errors with alerts tied to runbooks
  • Backup Knack objects daily and keep 90-day retention for recovery
  • Keep bundle size limits and cache warming part of CI to preserve UX

Example use cases

  • Trigger a production redeploy automatically after rotating Knack API keys
  • Start latency monitor to alert when API responses exceed 2s and log metrics
  • Sync production Knack credentials to a staging copy while excluding user tokens
  • Run daily cron backup of selected Knack objects to S3 and clean older snapshots
  • CI pipeline that tests Knack connectivity, validates bundle size, then deploys to Vercel

FAQ

How does rollback work if a deployment breaks?

The skill lists recent Vercel deployments, picks the prior successful deployment, and promotes it to production to restore the previous state.

How are sensitive keys handled during environment sync?

Sync filters support include/exclude lists. Sensitive_keys and user tokens are excluded and secrets are stored only in Vercel environment variables; rotate and redeploy after generating new keys.