centralize guides and rails under mosaic with runtime compatibility links
This commit is contained in:
144
guides/authentication.md
Normal file
144
guides/authentication.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Authentication & Authorization Guide
|
||||
|
||||
## Before Starting
|
||||
1. Check assigned issue: `~/.mosaic/rails/git/issue-list.sh -a @me`
|
||||
2. Review existing auth implementation in codebase
|
||||
3. Review Vault secrets structure: `docs/vault-secrets-structure.md`
|
||||
|
||||
## Authentication Patterns
|
||||
|
||||
### JWT (JSON Web Tokens)
|
||||
```
|
||||
Vault Path: secret-{env}/backend-api/jwt/signing-key
|
||||
Fields: key, algorithm, expiry_seconds
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use RS256 or ES256 (asymmetric) for distributed systems
|
||||
- Use HS256 (symmetric) only for single-service auth
|
||||
- Set reasonable expiry (15min-1hr for access tokens)
|
||||
- Include minimal claims (sub, exp, iat, roles)
|
||||
- Never store sensitive data in JWT payload
|
||||
|
||||
### Session-Based
|
||||
```
|
||||
Vault Path: secret-{env}/{service}/session/secret
|
||||
Fields: secret, cookie_name, max_age
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use secure, httpOnly, sameSite cookies
|
||||
- Regenerate session ID on privilege change
|
||||
- Implement session timeout
|
||||
- Store sessions server-side (Redis/database)
|
||||
|
||||
### OAuth2/OIDC
|
||||
```
|
||||
Vault Paths:
|
||||
- secret-{env}/{service}/oauth/{provider}/client_id
|
||||
- secret-{env}/{service}/oauth/{provider}/client_secret
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use PKCE for public clients
|
||||
- Validate state parameter
|
||||
- Verify token signatures
|
||||
- Check issuer and audience claims
|
||||
|
||||
## Authorization Patterns
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
```python
|
||||
# Example middleware
|
||||
def require_role(roles: list):
|
||||
def decorator(handler):
|
||||
def wrapper(request):
|
||||
user_roles = get_user_roles(request.user_id)
|
||||
if not any(role in user_roles for role in roles):
|
||||
raise ForbiddenError()
|
||||
return handler(request)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@require_role(['admin', 'moderator'])
|
||||
def delete_user(request):
|
||||
pass
|
||||
```
|
||||
|
||||
### Permission-Based
|
||||
```python
|
||||
# Check specific permissions
|
||||
def check_permission(user_id, resource, action):
|
||||
permissions = get_user_permissions(user_id)
|
||||
return f"{resource}:{action}" in permissions
|
||||
```
|
||||
|
||||
## Security Requirements
|
||||
|
||||
### Password Handling
|
||||
- Use bcrypt, scrypt, or Argon2 for hashing
|
||||
- Minimum 12 character passwords
|
||||
- Check against breached password lists
|
||||
- Implement account lockout after failed attempts
|
||||
|
||||
### Token Security
|
||||
- Rotate secrets regularly
|
||||
- Implement token revocation
|
||||
- Use short-lived access tokens with refresh tokens
|
||||
- Store refresh tokens securely (httpOnly cookies or encrypted storage)
|
||||
|
||||
### Multi-Factor Authentication
|
||||
- Support TOTP (Google Authenticator compatible)
|
||||
- Consider WebAuthn for passwordless
|
||||
- Require MFA for sensitive operations
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
### Test Cases Required
|
||||
```python
|
||||
class TestAuthentication:
|
||||
def test_login_success_returns_token(self):
|
||||
pass
|
||||
def test_login_failure_returns_401(self):
|
||||
pass
|
||||
def test_invalid_token_returns_401(self):
|
||||
pass
|
||||
def test_expired_token_returns_401(self):
|
||||
pass
|
||||
def test_missing_token_returns_401(self):
|
||||
pass
|
||||
def test_insufficient_permissions_returns_403(self):
|
||||
pass
|
||||
def test_token_refresh_works(self):
|
||||
pass
|
||||
def test_logout_invalidates_token(self):
|
||||
pass
|
||||
```
|
||||
|
||||
## Common Vulnerabilities to Avoid
|
||||
|
||||
1. **Broken Authentication**
|
||||
- Weak password requirements
|
||||
- Missing brute-force protection
|
||||
- Session fixation
|
||||
|
||||
2. **Broken Access Control**
|
||||
- Missing authorization checks
|
||||
- IDOR (Insecure Direct Object Reference)
|
||||
- Privilege escalation
|
||||
|
||||
3. **Security Misconfiguration**
|
||||
- Default credentials
|
||||
- Verbose error messages
|
||||
- Missing security headers
|
||||
|
||||
## Commit Format
|
||||
```
|
||||
feat(#89): Implement JWT authentication
|
||||
|
||||
- Add /auth/login and /auth/refresh endpoints
|
||||
- Implement token validation middleware
|
||||
- Configure 15min access token expiry
|
||||
|
||||
Fixes #89
|
||||
```
|
||||
111
guides/backend.md
Normal file
111
guides/backend.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Backend Development Guide
|
||||
|
||||
## Before Starting
|
||||
1. Check assigned issue: `~/.mosaic/rails/git/issue-list.sh -a @me`
|
||||
2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md`
|
||||
3. Review API contracts and database schema
|
||||
|
||||
## Development Standards
|
||||
|
||||
### API Design
|
||||
- Follow RESTful conventions (or GraphQL patterns if applicable)
|
||||
- Use consistent endpoint naming: `/api/v1/resource-name`
|
||||
- Return appropriate HTTP status codes
|
||||
- Include pagination for list endpoints
|
||||
- Document all endpoints (OpenAPI/Swagger preferred)
|
||||
|
||||
### Database
|
||||
- Write migrations for schema changes
|
||||
- Use parameterized queries (prevent SQL injection)
|
||||
- Index frequently queried columns
|
||||
- Document relationships and constraints
|
||||
|
||||
### Error Handling
|
||||
- Return structured error responses
|
||||
- Log errors with context (request ID, user ID if applicable)
|
||||
- Never expose internal errors to clients
|
||||
- Use appropriate error codes
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "User-friendly message",
|
||||
"details": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
- Validate all input at API boundaries
|
||||
- Implement rate limiting on public endpoints
|
||||
- Use secrets from Vault (see `docs/vault-secrets-structure.md`)
|
||||
- Never log sensitive data (passwords, tokens, PII)
|
||||
- Follow OWASP guidelines
|
||||
|
||||
### Authentication/Authorization
|
||||
- Use project's established auth pattern
|
||||
- Validate tokens on every request
|
||||
- Check permissions before operations
|
||||
- See `~/.mosaic/guides/authentication.md` for details
|
||||
|
||||
## Testing Requirements (TDD)
|
||||
1. Write tests BEFORE implementation
|
||||
2. Minimum 85% coverage
|
||||
3. Test categories:
|
||||
- Unit tests for business logic
|
||||
- Integration tests for API endpoints
|
||||
- Database tests with transactions/rollback
|
||||
|
||||
### Test Patterns
|
||||
```python
|
||||
# API test example structure
|
||||
class TestResourceEndpoint:
|
||||
def test_create_returns_201(self):
|
||||
pass
|
||||
def test_create_validates_input(self):
|
||||
pass
|
||||
def test_get_returns_404_for_missing(self):
|
||||
pass
|
||||
def test_requires_authentication(self):
|
||||
pass
|
||||
```
|
||||
|
||||
## Code Style
|
||||
- Follow Google Style Guide for your language
|
||||
- **TypeScript: Follow `~/.mosaic/guides/typescript.md` — MANDATORY**
|
||||
- Use linter/formatter from project configuration
|
||||
- Keep functions focused and small
|
||||
- Document complex business logic
|
||||
|
||||
### TypeScript Quick Rules (see typescript.md for full guide)
|
||||
- **NO `any`** — define explicit types always
|
||||
- **NO lazy `unknown`** — only for error catches and external data with validation
|
||||
- **Explicit return types** on all exported functions
|
||||
- **Explicit parameter types** always
|
||||
- **Interface for DTOs** — never inline object types
|
||||
- **Typed errors** — use custom error classes
|
||||
|
||||
## Performance
|
||||
- Use database connection pooling
|
||||
- Implement caching where appropriate
|
||||
- Profile slow endpoints
|
||||
- Use async operations for I/O
|
||||
|
||||
## Commit Format
|
||||
```
|
||||
feat(#45): Add user registration endpoint
|
||||
|
||||
- POST /api/v1/users for registration
|
||||
- Email validation and uniqueness check
|
||||
- Password hashing with bcrypt
|
||||
|
||||
Fixes #45
|
||||
```
|
||||
|
||||
## Before Completing
|
||||
1. Run full test suite
|
||||
2. Verify migrations work (up and down)
|
||||
3. Test API with curl/httpie
|
||||
4. Update scratchpad with completion notes
|
||||
5. Reference issue in commit
|
||||
338
guides/bootstrap.md
Normal file
338
guides/bootstrap.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Project Bootstrap Guide
|
||||
|
||||
> Load this guide when setting up a new project for AI-assisted development.
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers how to bootstrap a project so AI agents (Claude, Codex, etc.) can work on it effectively. Proper bootstrapping ensures:
|
||||
|
||||
1. Agents understand the project structure and conventions
|
||||
2. Orchestration works correctly with quality gates
|
||||
3. Independent code review and security review are configured
|
||||
4. Issue tracking is consistent across projects
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Automated bootstrap (recommended)
|
||||
~/.mosaic/rails/bootstrap/init-project.sh \
|
||||
--name "my-project" \
|
||||
--type "nestjs-nextjs" \
|
||||
--repo "https://git.mosaicstack.dev/owner/repo"
|
||||
|
||||
# Or manually using templates
|
||||
export PROJECT_NAME="My Project"
|
||||
export PROJECT_DESCRIPTION="What this project does"
|
||||
export TASK_PREFIX="MP"
|
||||
envsubst < ~/.mosaic/templates/agent/CLAUDE.md.template > CLAUDE.md
|
||||
envsubst < ~/.mosaic/templates/agent/AGENTS.md.template > AGENTS.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Detect Project Type
|
||||
|
||||
Check what files exist in the project root to determine the type:
|
||||
|
||||
| File Present | Project Type | Template |
|
||||
|---|---|---|
|
||||
| `package.json` + `pnpm-workspace.yaml` + NestJS+Next.js | NestJS + Next.js Monorepo | `projects/nestjs-nextjs/` |
|
||||
| `pyproject.toml` + `manage.py` | Django | `projects/django/` |
|
||||
| `pyproject.toml` (no Django) | Python (generic) | Generic template |
|
||||
| `package.json` (no monorepo) | Node.js (generic) | Generic template |
|
||||
| Other | Generic | Generic template |
|
||||
|
||||
```bash
|
||||
# Auto-detect project type
|
||||
detect_project_type() {
|
||||
if [[ -f "pnpm-workspace.yaml" ]] && [[ -f "turbo.json" ]]; then
|
||||
# Check for NestJS + Next.js
|
||||
if grep -q "nestjs" package.json 2>/dev/null && grep -q "next" package.json 2>/dev/null; then
|
||||
echo "nestjs-nextjs"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
if [[ -f "manage.py" ]] && [[ -f "pyproject.toml" ]]; then
|
||||
echo "django"
|
||||
return
|
||||
fi
|
||||
if [[ -f "pyproject.toml" ]]; then
|
||||
echo "python"
|
||||
return
|
||||
fi
|
||||
if [[ -f "package.json" ]]; then
|
||||
echo "nodejs"
|
||||
return
|
||||
fi
|
||||
echo "generic"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create CLAUDE.md
|
||||
|
||||
The `CLAUDE.md` file is the primary configuration file for AI agents. It tells them everything they need to know about the project.
|
||||
|
||||
### Using a Tech-Stack Template
|
||||
|
||||
```bash
|
||||
# Set variables
|
||||
export PROJECT_NAME="My Project"
|
||||
export PROJECT_DESCRIPTION="Multi-tenant SaaS platform"
|
||||
export PROJECT_DIR="my-project"
|
||||
export REPO_URL="https://git.mosaicstack.dev/owner/repo"
|
||||
export TASK_PREFIX="MP"
|
||||
|
||||
# Use tech-stack-specific template if available
|
||||
TYPE=$(detect_project_type)
|
||||
TEMPLATE_DIR="$HOME/.mosaic/templates/agent/projects/$TYPE"
|
||||
|
||||
if [[ -d "$TEMPLATE_DIR" ]]; then
|
||||
envsubst < "$TEMPLATE_DIR/CLAUDE.md.template" > CLAUDE.md
|
||||
else
|
||||
envsubst < "$HOME/.mosaic/templates/agent/CLAUDE.md.template" > CLAUDE.md
|
||||
fi
|
||||
```
|
||||
|
||||
### Using the Generic Template
|
||||
|
||||
```bash
|
||||
# Set all required variables
|
||||
export PROJECT_NAME="My Project"
|
||||
export PROJECT_DESCRIPTION="What this project does"
|
||||
export REPO_URL="https://git.mosaicstack.dev/owner/repo"
|
||||
export PROJECT_DIR="my-project"
|
||||
export SOURCE_DIR="src"
|
||||
export CONFIG_FILES="pyproject.toml / package.json"
|
||||
export FRONTEND_STACK="N/A"
|
||||
export BACKEND_STACK="Python / FastAPI"
|
||||
export DATABASE_STACK="PostgreSQL"
|
||||
export TESTING_STACK="pytest"
|
||||
export DEPLOYMENT_STACK="Docker"
|
||||
export BUILD_COMMAND="pip install -e ."
|
||||
export TEST_COMMAND="pytest tests/"
|
||||
export LINT_COMMAND="ruff check ."
|
||||
export TYPECHECK_COMMAND="mypy ."
|
||||
export QUALITY_GATES="ruff check . && mypy . && pytest tests/"
|
||||
|
||||
envsubst < ~/.mosaic/templates/agent/CLAUDE.md.template > CLAUDE.md
|
||||
```
|
||||
|
||||
### Required Sections
|
||||
|
||||
Every CLAUDE.md should contain:
|
||||
|
||||
1. **Project description** — One-line summary
|
||||
2. **Conditional documentation loading** — Table of guides
|
||||
3. **Technology stack** — What's used
|
||||
4. **Repository structure** — Directory tree
|
||||
5. **Development workflow** — Branch strategy, build, test
|
||||
6. **Quality gates** — Commands that must pass
|
||||
7. **Issue tracking** — Commit format, labels
|
||||
8. **Code review** — Codex and fallback review commands
|
||||
9. **Secrets management** — How secrets are handled
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create AGENTS.md
|
||||
|
||||
The `AGENTS.md` file contains agent-specific patterns, gotchas, and orchestrator integration details.
|
||||
|
||||
```bash
|
||||
TYPE=$(detect_project_type)
|
||||
TEMPLATE_DIR="$HOME/.mosaic/templates/agent/projects/$TYPE"
|
||||
|
||||
if [[ -d "$TEMPLATE_DIR" ]]; then
|
||||
envsubst < "$TEMPLATE_DIR/AGENTS.md.template" > AGENTS.md
|
||||
else
|
||||
envsubst < "$HOME/.mosaic/templates/agent/AGENTS.md.template" > AGENTS.md
|
||||
fi
|
||||
```
|
||||
|
||||
### Living Document
|
||||
|
||||
AGENTS.md is a **living document**. Agents should update it when they discover:
|
||||
- Reusable patterns (how similar features are built)
|
||||
- Non-obvious requirements (e.g., "frontend env vars need NEXT_PUBLIC_ prefix")
|
||||
- Common gotchas (e.g., "run migrations after schema changes")
|
||||
- Testing approaches specific to this project
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Directory Structure
|
||||
|
||||
```bash
|
||||
# Create standard directories
|
||||
mkdir -p docs/scratchpads
|
||||
mkdir -p docs/templates
|
||||
mkdir -p docs/reports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Initialize Repository Labels & Milestones
|
||||
|
||||
```bash
|
||||
# Use the init script
|
||||
~/.mosaic/rails/bootstrap/init-repo-labels.sh
|
||||
|
||||
# Or manually create standard labels
|
||||
~/.mosaic/rails/git/issue-create.sh # (labels are created on first use)
|
||||
```
|
||||
|
||||
### Standard Labels
|
||||
|
||||
| Label | Color | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `epic` | `#3E4B9E` | Large feature spanning multiple issues |
|
||||
| `feature` | `#0E8A16` | New functionality |
|
||||
| `bug` | `#D73A4A` | Defect fix |
|
||||
| `task` | `#0075CA` | General work item |
|
||||
| `documentation` | `#0075CA` | Documentation updates |
|
||||
| `security` | `#B60205` | Security-related |
|
||||
| `breaking` | `#D93F0B` | Breaking change |
|
||||
|
||||
### Initial Milestone
|
||||
|
||||
Create the first milestone for MVP:
|
||||
|
||||
```bash
|
||||
~/.mosaic/rails/git/milestone-create.sh -t "0.1.0" -d "MVP - Minimum Viable Product"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Set Up CI/CD Review Pipeline
|
||||
|
||||
### Woodpecker CI
|
||||
|
||||
```bash
|
||||
# Copy Codex review pipeline
|
||||
mkdir -p .woodpecker/schemas
|
||||
cp ~/.mosaic/rails/codex/woodpecker/codex-review.yml .woodpecker/
|
||||
cp ~/.mosaic/rails/codex/schemas/*.json .woodpecker/schemas/
|
||||
|
||||
# Add codex_api_key secret to Woodpecker CI dashboard
|
||||
```
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
For GitHub repos, use the official Codex GitHub Action instead:
|
||||
```yaml
|
||||
# .github/workflows/codex-review.yml
|
||||
uses: openai/codex-action@v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Verify Bootstrap
|
||||
|
||||
After bootstrapping, verify everything works:
|
||||
|
||||
```bash
|
||||
# Check files exist
|
||||
ls CLAUDE.md AGENTS.md docs/scratchpads/
|
||||
|
||||
# Verify CLAUDE.md has required sections
|
||||
grep -c "Quality Gates" CLAUDE.md
|
||||
grep -c "Technology Stack" CLAUDE.md
|
||||
grep -c "Code Review" CLAUDE.md
|
||||
|
||||
# Verify quality gates run
|
||||
eval "$(grep -A1 'Quality Gates' CLAUDE.md | tail -1)"
|
||||
|
||||
# Test Codex review (if configured)
|
||||
~/.mosaic/rails/codex/codex-code-review.sh --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Templates
|
||||
|
||||
### Generic Templates
|
||||
|
||||
| Template | Path | Purpose |
|
||||
|----------|------|---------|
|
||||
| `CLAUDE.md.template` | `~/.mosaic/templates/agent/` | Generic project CLAUDE.md |
|
||||
| `AGENTS.md.template` | `~/.mosaic/templates/agent/` | Generic agent context |
|
||||
|
||||
### Tech-Stack Templates
|
||||
|
||||
| Stack | Path | Includes |
|
||||
|-------|------|----------|
|
||||
| NestJS + Next.js | `~/.mosaic/templates/agent/projects/nestjs-nextjs/` | CLAUDE.md, AGENTS.md |
|
||||
| Django | `~/.mosaic/templates/agent/projects/django/` | CLAUDE.md, AGENTS.md |
|
||||
|
||||
### Orchestrator Templates
|
||||
|
||||
| Template | Path | Purpose |
|
||||
|----------|------|---------|
|
||||
| `tasks.md.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Task tracking |
|
||||
| `orchestrator-learnings.json.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Variance tracking |
|
||||
| `phase-issue-body.md.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Gitea issue body |
|
||||
| `scratchpad.md.template` | `~/src/jarvis-brain/docs/templates/` | Per-task working doc |
|
||||
|
||||
### Variables Reference
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `${PROJECT_NAME}` | Human-readable project name | "Mosaic Stack" |
|
||||
| `${PROJECT_DESCRIPTION}` | One-line description | "Multi-tenant platform" |
|
||||
| `${PROJECT_DIR}` | Directory name | "mosaic-stack" |
|
||||
| `${PROJECT_SLUG}` | Python package slug | "mosaic_stack" |
|
||||
| `${REPO_URL}` | Git remote URL | "https://git.mosaicstack.dev/mosaic/stack" |
|
||||
| `${TASK_PREFIX}` | Orchestrator task prefix | "MS" |
|
||||
| `${SOURCE_DIR}` | Source code directory | "src" or "apps" |
|
||||
| `${QUALITY_GATES}` | Quality gate commands | "pnpm typecheck && pnpm lint && pnpm test" |
|
||||
| `${BUILD_COMMAND}` | Build command | "pnpm build" |
|
||||
| `${TEST_COMMAND}` | Test command | "pnpm test" |
|
||||
| `${LINT_COMMAND}` | Lint command | "pnpm lint" |
|
||||
| `${TYPECHECK_COMMAND}` | Type check command | "pnpm typecheck" |
|
||||
| `${FRONTEND_STACK}` | Frontend technologies | "Next.js + React" |
|
||||
| `${BACKEND_STACK}` | Backend technologies | "NestJS + Prisma" |
|
||||
| `${DATABASE_STACK}` | Database technologies | "PostgreSQL" |
|
||||
| `${TESTING_STACK}` | Testing technologies | "Vitest + Playwright" |
|
||||
| `${DEPLOYMENT_STACK}` | Deployment technologies | "Docker" |
|
||||
| `${CONFIG_FILES}` | Key config files | "package.json, tsconfig.json" |
|
||||
|
||||
---
|
||||
|
||||
## Bootstrap Scripts
|
||||
|
||||
### init-project.sh
|
||||
|
||||
Full project bootstrap with interactive and flag-based modes:
|
||||
|
||||
```bash
|
||||
~/.mosaic/rails/bootstrap/init-project.sh \
|
||||
--name "My Project" \
|
||||
--type "nestjs-nextjs" \
|
||||
--repo "https://git.mosaicstack.dev/owner/repo" \
|
||||
--prefix "MP" \
|
||||
--description "Multi-tenant platform"
|
||||
```
|
||||
|
||||
### init-repo-labels.sh
|
||||
|
||||
Initialize standard labels and MVP milestone:
|
||||
|
||||
```bash
|
||||
~/.mosaic/rails/bootstrap/init-repo-labels.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
After bootstrapping, verify:
|
||||
|
||||
- [ ] `CLAUDE.md` exists with all required sections
|
||||
- [ ] `AGENTS.md` exists with patterns and quality gates
|
||||
- [ ] `docs/scratchpads/` directory exists
|
||||
- [ ] Git labels created (epic, feature, bug, task, etc.)
|
||||
- [ ] Initial milestone created (0.1.0 MVP)
|
||||
- [ ] Quality gates run successfully
|
||||
- [ ] `.env.example` exists (if project uses env vars)
|
||||
- [ ] CI/CD pipeline configured (if using Woodpecker/GitHub Actions)
|
||||
- [ ] Codex review scripts accessible (`~/.mosaic/rails/codex/`)
|
||||
904
guides/ci-cd-pipelines.md
Normal file
904
guides/ci-cd-pipelines.md
Normal file
@@ -0,0 +1,904 @@
|
||||
# CI/CD Pipeline Guide
|
||||
|
||||
> **Load this guide when:** Adding Docker build/push steps, configuring Woodpecker CI pipelines, publishing packages to registries, or implementing CI/CD for a new project.
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers the canonical CI/CD pattern used across projects. The pipeline runs in Woodpecker CI and follows this flow:
|
||||
|
||||
```
|
||||
GIT PUSH
|
||||
↓
|
||||
QUALITY GATES (lint, typecheck, test, audit)
|
||||
↓ all pass
|
||||
BUILD (compile all packages)
|
||||
↓ only on main/develop/tags
|
||||
DOCKER BUILD & PUSH (Kaniko → Gitea Container Registry)
|
||||
↓ all images pushed
|
||||
PACKAGE LINKING (associate images with repository in Gitea)
|
||||
```
|
||||
|
||||
## Reference Implementations
|
||||
|
||||
### Split Pipelines (Preferred for Monorepos)
|
||||
|
||||
**Mosaic Telemetry** (`~/src/mosaic-telemetry-monorepo/.woodpecker/`) is the canonical example of **split per-package pipelines** with path filtering, full security chain (source + container scanning), and efficient CI resource usage.
|
||||
|
||||
**Key features:**
|
||||
- One YAML per package in `.woodpecker/` directory
|
||||
- Path filtering: only the affected package's pipeline runs on push
|
||||
- Security chain: source scanning (bandit/npm audit) + dependency audit (pip-audit) + container scanning (Trivy)
|
||||
- Docker build gates on ALL quality steps
|
||||
|
||||
**Always use this pattern for monorepos.** It saves CI minutes and isolates failures.
|
||||
|
||||
### Single Pipeline (Legacy/Simple Projects)
|
||||
|
||||
**Mosaic Stack** (`~/src/mosaic-stack/.woodpecker/build.yml`) uses a single pipeline that builds everything on every push. This works but wastes CI resources on large monorepos. **Mosaic Stack is scheduled for migration to split pipelines.**
|
||||
|
||||
Always read the telemetry pipelines first when implementing a new pipeline.
|
||||
|
||||
## Infrastructure Instances
|
||||
|
||||
| Project | Gitea | Woodpecker | Registry |
|
||||
|---------|-------|------------|----------|
|
||||
| Mosaic Stack | `git.mosaicstack.dev` | `ci.mosaicstack.dev` | `git.mosaicstack.dev` |
|
||||
| U-Connect | `git.uscllc.com` | `woodpecker.uscllc.net` | `git.uscllc.com` |
|
||||
|
||||
The patterns are identical — only the hostnames and org/repo names differ.
|
||||
|
||||
## Woodpecker Pipeline Structure
|
||||
|
||||
### YAML Anchors (DRY)
|
||||
|
||||
Define reusable values at the top of `.woodpecker.yml`:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
- &node_image "node:20-alpine"
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci
|
||||
# For pnpm projects, use:
|
||||
# - &install_deps |
|
||||
# corepack enable
|
||||
# pnpm install --frozen-lockfile
|
||||
- &kaniko_setup |
|
||||
mkdir -p /kaniko/.docker
|
||||
echo "{\"auths\":{\"REGISTRY_HOST\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
|
||||
```
|
||||
|
||||
Replace `REGISTRY_HOST` with the actual Gitea hostname (e.g., `git.uscllc.com`).
|
||||
|
||||
### Step Dependencies
|
||||
|
||||
Woodpecker runs steps in parallel by default. Use `depends_on` to create the dependency graph:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
install:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
|
||||
lint:
|
||||
image: *node_image
|
||||
commands:
|
||||
- npm run lint
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
typecheck:
|
||||
image: *node_image
|
||||
commands:
|
||||
- npm run type-check
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
test:
|
||||
image: *node_image
|
||||
commands:
|
||||
- npm run test
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
build:
|
||||
image: *node_image
|
||||
environment:
|
||||
NODE_ENV: "production"
|
||||
commands:
|
||||
- npm run build
|
||||
depends_on:
|
||||
- lint
|
||||
- typecheck
|
||||
- test
|
||||
```
|
||||
|
||||
### Conditional Execution
|
||||
|
||||
Use `when` clauses to limit expensive steps (Docker builds) to relevant branches:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
# Top-level: run quality gates on everything
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
# Per-step: only build Docker images on main/develop/tags
|
||||
docker-build-api:
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
```
|
||||
|
||||
## Docker Build & Push with Kaniko
|
||||
|
||||
### Why Kaniko
|
||||
|
||||
Kaniko builds container images without requiring a Docker daemon. This is the standard approach in Woodpecker CI because:
|
||||
- No privileged mode needed
|
||||
- No Docker-in-Docker security concerns
|
||||
- Multi-destination tagging in a single build
|
||||
- Works in any container runtime
|
||||
|
||||
### Kaniko Step Template
|
||||
|
||||
```yaml
|
||||
docker-build-SERVICE:
|
||||
image: gcr.io/kaniko-project/executor:debug
|
||||
environment:
|
||||
GITEA_USER:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination REGISTRY/ORG/IMAGE_NAME:${CI_COMMIT_SHA:0:8}"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:dev"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile PATH/TO/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- build
|
||||
```
|
||||
|
||||
**Replace these placeholders:**
|
||||
|
||||
| Placeholder | Example (Mosaic) | Example (U-Connect) |
|
||||
|-------------|-------------------|----------------------|
|
||||
| `REGISTRY` | `git.mosaicstack.dev` | `git.uscllc.com` |
|
||||
| `ORG` | `mosaic` | `usc` |
|
||||
| `IMAGE_NAME` | `stack-api` | `uconnect-backend-api` |
|
||||
| `PATH/TO/Dockerfile` | `apps/api/Dockerfile` | `src/backend-api/Dockerfile` |
|
||||
|
||||
### Image Tagging Strategy
|
||||
|
||||
Every build produces multiple tags:
|
||||
|
||||
| Condition | Tag | Purpose |
|
||||
|-----------|-----|---------|
|
||||
| Always | `${CI_COMMIT_SHA:0:8}` | Immutable reference to exact commit |
|
||||
| `main` branch | `latest` | Current production release |
|
||||
| `develop` branch | `dev` | Current development build |
|
||||
| Git tag (e.g., `v1.0.0`) | `v1.0.0` | Semantic version release |
|
||||
|
||||
### Kaniko Options
|
||||
|
||||
Common flags for `/kaniko/executor`:
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--context .` | Build context directory |
|
||||
| `--dockerfile path/Dockerfile` | Dockerfile location |
|
||||
| `--destination registry/org/image:tag` | Push target (repeatable) |
|
||||
| `--build-arg KEY=VALUE` | Pass build arguments |
|
||||
| `--cache=true` | Enable layer caching |
|
||||
| `--cache-repo registry/org/image-cache` | Cache storage location |
|
||||
|
||||
### Build Arguments
|
||||
|
||||
Pass environment-specific values at build time:
|
||||
|
||||
```yaml
|
||||
/kaniko/executor --context . --dockerfile apps/web/Dockerfile \
|
||||
--build-arg NEXT_PUBLIC_API_URL=https://api.example.com \
|
||||
$DESTINATIONS
|
||||
```
|
||||
|
||||
## Gitea Container Registry
|
||||
|
||||
### How It Works
|
||||
|
||||
Gitea has a built-in container registry. When you push an image to `git.example.com/org/image:tag`, Gitea stores it and makes it available in the Packages section.
|
||||
|
||||
### Authentication
|
||||
|
||||
Kaniko authenticates via a Docker config file created at pipeline start:
|
||||
|
||||
```json
|
||||
{
|
||||
"auths": {
|
||||
"git.example.com": {
|
||||
"username": "GITEA_USER",
|
||||
"password": "GITEA_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The token must have `package:write` scope. Generate it at: `https://GITEA_HOST/user/settings/applications`
|
||||
|
||||
### Pulling Images
|
||||
|
||||
After pushing, images are available at:
|
||||
|
||||
```bash
|
||||
docker pull git.example.com/org/image:tag
|
||||
```
|
||||
|
||||
In `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
api:
|
||||
image: git.example.com/org/image:${IMAGE_TAG:-dev}
|
||||
```
|
||||
|
||||
## Package Linking
|
||||
|
||||
After pushing images to the Gitea registry, link them to the source repository so they appear on the repository's Packages tab.
|
||||
|
||||
### Gitea Package Linking API
|
||||
|
||||
```
|
||||
POST /api/v1/packages/{owner}/{type}/{name}/-/link/{repo}
|
||||
```
|
||||
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| `owner` | Organization name (e.g., `mosaic`, `usc`) |
|
||||
| `type` | `container` |
|
||||
| `name` | Image name (e.g., `stack-api`) |
|
||||
| `repo` | Repository name (e.g., `stack`, `uconnect`) |
|
||||
|
||||
### Link Step Template
|
||||
|
||||
```yaml
|
||||
link-packages:
|
||||
image: alpine:3
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- apk add --no-cache curl
|
||||
- echo "Waiting 10 seconds for packages to be indexed..."
|
||||
- sleep 10
|
||||
- |
|
||||
set -e
|
||||
link_package() {
|
||||
PKG="$$1"
|
||||
echo "Linking $$PKG..."
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token $$GITEA_TOKEN" \
|
||||
"https://GITEA_HOST/api/v1/packages/ORG/container/$$PKG/-/link/REPO")
|
||||
|
||||
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
|
||||
echo " Linked $$PKG"
|
||||
return 0
|
||||
elif [ "$$STATUS" = "400" ]; then
|
||||
echo " $$PKG already linked"
|
||||
return 0
|
||||
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
|
||||
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
|
||||
sleep 5
|
||||
else
|
||||
echo " FAILED: $$PKG status $$STATUS"
|
||||
cat /tmp/link-response.txt
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
link_package "image-name-1"
|
||||
link_package "image-name-2"
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-image-1
|
||||
- docker-build-image-2
|
||||
```
|
||||
|
||||
**Replace:** `GITEA_HOST`, `ORG`, `REPO`, and the `link_package` calls with actual image names.
|
||||
|
||||
**Note on `$$`:** Woodpecker uses `$$` to escape `$` in shell commands within YAML. Use `$$` for shell variables and `${CI_*}` (single `$`) for Woodpecker CI variables.
|
||||
|
||||
### Status Codes
|
||||
|
||||
| Code | Meaning | Action |
|
||||
|------|---------|--------|
|
||||
| 201 | Created | Success |
|
||||
| 204 | No content | Success |
|
||||
| 400 | Bad request | Already linked (OK) |
|
||||
| 404 | Not found | Retry — package may not be indexed yet |
|
||||
|
||||
### Known Issue
|
||||
|
||||
The Gitea package linking API (added in Gitea 1.24.0) can return 404 for recently pushed packages. The retry logic with 5-second delays handles this. If linking still fails, packages are usable — they just won't appear on the repository Packages tab. They can be linked manually via the Gitea web UI.
|
||||
|
||||
## Woodpecker Secrets
|
||||
|
||||
### Required Secrets
|
||||
|
||||
Configure these in the Woodpecker UI (Settings > Secrets) or via CLI:
|
||||
|
||||
| Secret Name | Value | Scope |
|
||||
|-------------|-------|-------|
|
||||
| `gitea_username` | Gitea username or service account | `push`, `manual`, `tag` |
|
||||
| `gitea_token` | Gitea token with `package:write` scope | `push`, `manual`, `tag` |
|
||||
|
||||
### Setting Secrets via CLI
|
||||
|
||||
```bash
|
||||
# Woodpecker CLI
|
||||
woodpecker secret add ORG/REPO --name gitea_username --value "USERNAME"
|
||||
woodpecker secret add ORG/REPO --name gitea_token --value "TOKEN"
|
||||
```
|
||||
|
||||
### Security Rules
|
||||
|
||||
- Never hardcode tokens in pipeline YAML
|
||||
- Use `from_secret` for all credentials
|
||||
- Limit secret event scope (don't expose on `pull_request` from forks)
|
||||
- Use dedicated service accounts, not personal tokens
|
||||
- Rotate tokens periodically
|
||||
|
||||
## npm Package Publishing
|
||||
|
||||
For projects with publishable npm packages (e.g., shared libraries, design systems).
|
||||
|
||||
### Publishing to Gitea npm Registry
|
||||
|
||||
Gitea includes a built-in npm registry at `https://GITEA_HOST/api/packages/ORG/npm/`.
|
||||
|
||||
**Pipeline step:**
|
||||
|
||||
```yaml
|
||||
publish-packages:
|
||||
image: *node_image
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- |
|
||||
echo "//GITEA_HOST/api/packages/ORG/npm/:_authToken=$$GITEA_TOKEN" > .npmrc
|
||||
echo "@SCOPE:registry=https://GITEA_HOST/api/packages/ORG/npm/" >> .npmrc
|
||||
- npm publish -w @SCOPE/package-name
|
||||
when:
|
||||
- branch: [main]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- build
|
||||
```
|
||||
|
||||
**Replace:** `GITEA_HOST`, `ORG`, `SCOPE`, `package-name`.
|
||||
|
||||
### Why Gitea npm (not Verdaccio)
|
||||
|
||||
Gitea's built-in npm registry eliminates the need for a separate Verdaccio instance. Benefits:
|
||||
|
||||
- **Same auth** — Gitea token with `package:write` scope works for git, containers, AND npm
|
||||
- **No extra service** — No Verdaccio container, no OAuth/Authentik integration, no separate compose stack
|
||||
- **Same UI** — Packages appear alongside container images in Gitea's Packages tab
|
||||
- **Same secrets** — `gitea_token` in Woodpecker handles both Docker push and npm publish
|
||||
|
||||
If a project currently uses Verdaccio (e.g., U-Connect at `npm.uscllc.net`), migrate to Gitea npm. See the migration checklist below.
|
||||
|
||||
### Versioning
|
||||
|
||||
Only publish when the version in `package.json` has changed. Add a version check:
|
||||
|
||||
```yaml
|
||||
commands:
|
||||
- |
|
||||
CURRENT=$(node -p "require('./src/PACKAGE/package.json').version")
|
||||
PUBLISHED=$(npm view @SCOPE/PACKAGE version 2>/dev/null || echo "0.0.0")
|
||||
if [ "$CURRENT" = "$PUBLISHED" ]; then
|
||||
echo "Version $CURRENT already published, skipping"
|
||||
exit 0
|
||||
fi
|
||||
echo "Publishing $CURRENT (was $PUBLISHED)"
|
||||
npm publish -w @SCOPE/PACKAGE
|
||||
```
|
||||
|
||||
## CI Services (Test Databases)
|
||||
|
||||
For projects that need a database during CI (migrations, integration tests):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: test_db
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
|
||||
steps:
|
||||
test:
|
||||
image: *node_image
|
||||
environment:
|
||||
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
|
||||
commands:
|
||||
- npm run test
|
||||
depends_on:
|
||||
- install
|
||||
```
|
||||
|
||||
The service name (`postgres`) becomes the hostname within the pipeline network.
|
||||
|
||||
## Split Pipelines for Monorepos (REQUIRED)
|
||||
|
||||
For any monorepo with multiple packages/apps, use **split pipelines** — one YAML per package in `.woodpecker/`.
|
||||
|
||||
### Why Split?
|
||||
|
||||
| Aspect | Single pipeline | Split pipelines |
|
||||
|--------|----------------|-----------------|
|
||||
| Path filtering | None — everything rebuilds | Per-package — only affected code |
|
||||
| Security scanning | Often missing | Required per-package |
|
||||
| CI minutes | Wasted on unaffected packages | Efficient |
|
||||
| Failure isolation | One failure blocks everything | Per-package failures isolated |
|
||||
| Readability | One massive file | Focused, maintainable |
|
||||
|
||||
### Structure
|
||||
|
||||
```
|
||||
.woodpecker/
|
||||
├── api.yml # Only runs when apps/api/** changes
|
||||
├── web.yml # Only runs when apps/web/** changes
|
||||
└── (infra.yml) # Optional: shared infra (DB images, etc.)
|
||||
```
|
||||
|
||||
**IMPORTANT:** Do NOT also have `.woodpecker.yml` at root — `.woodpecker/` directory takes precedence and the `.yml` file will be silently ignored.
|
||||
|
||||
### Path Filtering Template
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- event: [push, pull_request, manual]
|
||||
path:
|
||||
include: ['apps/api/**', '.woodpecker/api.yml']
|
||||
```
|
||||
|
||||
Each pipeline self-triggers on its own YAML changes. Manual triggers run regardless of path.
|
||||
|
||||
### Kaniko Context Scoping
|
||||
|
||||
In split pipelines, scope the Kaniko context to the app directory:
|
||||
|
||||
```yaml
|
||||
/kaniko/executor --context apps/api --dockerfile apps/api/Dockerfile $$DESTINATIONS
|
||||
```
|
||||
|
||||
This means Dockerfile `COPY . .` only copies the app's files, not the entire monorepo.
|
||||
|
||||
### Reference: Telemetry Split Pipeline
|
||||
|
||||
See `~/src/mosaic-telemetry-monorepo/.woodpecker/api.yml` and `web.yml` for a complete working example with path filtering, security chain, and Trivy scanning.
|
||||
|
||||
## Security Scanning (REQUIRED)
|
||||
|
||||
Every pipeline MUST include security scanning. Docker build steps MUST gate on all security steps passing.
|
||||
|
||||
### Source-Level Security (per tech stack)
|
||||
|
||||
**Python:**
|
||||
```yaml
|
||||
security-bandit:
|
||||
image: *uv_image
|
||||
commands:
|
||||
- |
|
||||
cd apps/api
|
||||
uv sync --all-extras --frozen
|
||||
uv run bandit -r src/ -f screen
|
||||
depends_on: [install]
|
||||
|
||||
security-audit:
|
||||
image: *uv_image
|
||||
commands:
|
||||
- |
|
||||
cd apps/api
|
||||
uv sync --all-extras --frozen
|
||||
uv run pip-audit
|
||||
depends_on: [install]
|
||||
```
|
||||
|
||||
**Node.js:**
|
||||
```yaml
|
||||
security-audit:
|
||||
image: node:22-alpine
|
||||
commands:
|
||||
- cd apps/web && npm audit --audit-level=high
|
||||
depends_on: [install]
|
||||
```
|
||||
|
||||
### Container Scanning (Trivy) — Post-Build
|
||||
|
||||
Run Trivy against every built image to catch OS-level and runtime vulnerabilities:
|
||||
|
||||
```yaml
|
||||
security-trivy:
|
||||
image: aquasec/trivy:latest
|
||||
environment:
|
||||
GITEA_USER:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
commands:
|
||||
- |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"auths\":{\"REGISTRY\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
|
||||
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
|
||||
REGISTRY/ORG/IMAGE:$${CI_COMMIT_SHA:0:8}
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-SERVICE
|
||||
```
|
||||
|
||||
**Replace:** `REGISTRY`, `ORG`, `IMAGE`, `SERVICE`.
|
||||
|
||||
### Full Dependency Chain
|
||||
|
||||
```
|
||||
install → [lint, typecheck, security-source, security-deps, test] → docker-build → trivy → link-package
|
||||
```
|
||||
|
||||
Docker build MUST depend on ALL quality + security steps. Trivy runs AFTER build. Package linking runs AFTER Trivy.
|
||||
|
||||
## Monorepo Considerations
|
||||
|
||||
### pnpm + Turbo (Mosaic Stack pattern)
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
steps:
|
||||
build:
|
||||
commands:
|
||||
- *install_deps
|
||||
- pnpm build # Turbo handles dependency order and caching
|
||||
```
|
||||
|
||||
### npm Workspaces (U-Connect pattern)
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci
|
||||
|
||||
steps:
|
||||
# Build shared dependencies first
|
||||
build-deps:
|
||||
commands:
|
||||
- npm run build -w @scope/shared-auth
|
||||
- npm run build -w @scope/shared-types
|
||||
|
||||
# Then build everything
|
||||
build-all:
|
||||
commands:
|
||||
- npm run build -w @scope/package-1
|
||||
- npm run build -w @scope/package-2
|
||||
# ... in dependency order
|
||||
depends_on:
|
||||
- build-deps
|
||||
```
|
||||
|
||||
### Per-Package Quality Checks
|
||||
|
||||
For large monorepos, run checks per-package in parallel:
|
||||
|
||||
```yaml
|
||||
lint-api:
|
||||
commands:
|
||||
- npm run lint -w @scope/api
|
||||
depends_on: [install]
|
||||
|
||||
lint-web:
|
||||
commands:
|
||||
- npm run lint -w @scope/web
|
||||
depends_on: [install]
|
||||
|
||||
# These run in parallel since they share the same dependency
|
||||
```
|
||||
|
||||
## Complete Pipeline Example
|
||||
|
||||
This is a minimal but complete pipeline for a project with two services:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
variables:
|
||||
- &node_image "node:20-alpine"
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci
|
||||
- &kaniko_setup |
|
||||
mkdir -p /kaniko/.docker
|
||||
echo "{\"auths\":{\"git.example.com\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
|
||||
|
||||
steps:
|
||||
# === Quality Gates ===
|
||||
install:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
|
||||
lint:
|
||||
image: *node_image
|
||||
commands:
|
||||
- npm run lint
|
||||
depends_on: [install]
|
||||
|
||||
test:
|
||||
image: *node_image
|
||||
commands:
|
||||
- npm run test
|
||||
depends_on: [install]
|
||||
|
||||
build:
|
||||
image: *node_image
|
||||
environment:
|
||||
NODE_ENV: "production"
|
||||
commands:
|
||||
- npm run build
|
||||
depends_on: [lint, test]
|
||||
|
||||
# === Docker Build & Push ===
|
||||
docker-build-api:
|
||||
image: gcr.io/kaniko-project/executor:debug
|
||||
environment:
|
||||
GITEA_USER:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination git.example.com/org/api:${CI_COMMIT_SHA:0:8}"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:dev"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile src/api/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on: [build]
|
||||
|
||||
docker-build-web:
|
||||
image: gcr.io/kaniko-project/executor:debug
|
||||
environment:
|
||||
GITEA_USER:
|
||||
from_secret: gitea_username
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
||||
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
||||
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
||||
commands:
|
||||
- *kaniko_setup
|
||||
- |
|
||||
DESTINATIONS="--destination git.example.com/org/web:${CI_COMMIT_SHA:0:8}"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:latest"
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:dev"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile src/web/Dockerfile $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on: [build]
|
||||
|
||||
# === Package Linking ===
|
||||
link-packages:
|
||||
image: alpine:3
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- apk add --no-cache curl
|
||||
- sleep 10
|
||||
- |
|
||||
set -e
|
||||
link_package() {
|
||||
PKG="$$1"
|
||||
for attempt in 1 2 3; do
|
||||
STATUS=$$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token $$GITEA_TOKEN" \
|
||||
"https://git.example.com/api/v1/packages/org/container/$$PKG/-/link/repo")
|
||||
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ] || [ "$$STATUS" = "400" ]; then
|
||||
echo "Linked $$PKG ($$STATUS)"
|
||||
return 0
|
||||
elif [ $$attempt -lt 3 ]; then
|
||||
sleep 5
|
||||
else
|
||||
echo "FAILED: $$PKG ($$STATUS)"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
link_package "api"
|
||||
link_package "web"
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
depends_on:
|
||||
- docker-build-api
|
||||
- docker-build-web
|
||||
```
|
||||
|
||||
## Checklist: Adding CI/CD to a Project
|
||||
|
||||
1. **Verify Dockerfiles exist** for each service that needs an image
|
||||
2. **Create Woodpecker secrets** (`gitea_username`, `gitea_token`) in the Woodpecker UI
|
||||
3. **Verify Gitea token scope** includes `package:write`
|
||||
4. **Add Docker build steps** to `.woodpecker.yml` using the Kaniko template above
|
||||
5. **Add package linking step** after all Docker builds
|
||||
6. **Update `docker-compose.yml`** to reference registry images instead of local builds:
|
||||
```yaml
|
||||
image: git.example.com/org/service:${IMAGE_TAG:-dev}
|
||||
```
|
||||
7. **Test on develop branch first** — push a small change and verify the pipeline
|
||||
8. **Verify images appear** in Gitea Packages tab after successful pipeline
|
||||
|
||||
## Gitea as Unified Platform
|
||||
|
||||
Gitea provides **three services in one**, eliminating the need for separate Harbor and Verdaccio deployments:
|
||||
|
||||
| Service | What Gitea Replaces | Registry URL |
|
||||
|---------|---------------------|-------------|
|
||||
| **Git hosting** | GitHub/GitLab | `https://GITEA_HOST/org/repo` |
|
||||
| **Container registry** | Harbor, Docker Hub | `docker pull GITEA_HOST/org/image:tag` |
|
||||
| **npm registry** | Verdaccio, Artifactory | `https://GITEA_HOST/api/packages/org/npm/` |
|
||||
|
||||
### Additional Package Types
|
||||
|
||||
Gitea also supports PyPI, Maven, NuGet, Cargo, Composer, Conan, Conda, Generic, and more. All use the same token authentication.
|
||||
|
||||
### Single Token, Three Services
|
||||
|
||||
A Gitea token with `package:write` scope handles:
|
||||
- `git push` / `git pull`
|
||||
- `docker push` / `docker pull` (container registry)
|
||||
- `npm publish` / `npm install` (npm registry)
|
||||
|
||||
This means a single `gitea_token` secret in Woodpecker CI covers all CI/CD operations.
|
||||
|
||||
### Architecture Simplification
|
||||
|
||||
**Before (3 services):**
|
||||
```
|
||||
Gitea (git) + Harbor (containers) + Verdaccio (npm)
|
||||
↓ separate auth ↓ separate auth ↓ OAuth/Authentik
|
||||
3 tokens 1 robot account 1 OIDC integration
|
||||
3 backup targets complex RBAC group-based access
|
||||
```
|
||||
|
||||
**After (1 service):**
|
||||
```
|
||||
Gitea (git + containers + npm)
|
||||
↓ single token
|
||||
1 secret in Woodpecker
|
||||
1 backup target
|
||||
unified RBAC via Gitea teams
|
||||
```
|
||||
|
||||
## Migrating from Verdaccio to Gitea npm
|
||||
|
||||
If a project currently uses Verdaccio (e.g., U-Connect at `npm.uscllc.net`), follow this migration checklist:
|
||||
|
||||
### Migration Steps
|
||||
|
||||
1. **Verify Gitea npm registry is accessible:**
|
||||
```bash
|
||||
curl -s https://GITEA_HOST/api/packages/ORG/npm/ | head -5
|
||||
```
|
||||
|
||||
2. **Update `.npmrc` in project root:**
|
||||
```ini
|
||||
# Before (Verdaccio)
|
||||
@uconnect:registry=https://npm.uscllc.net
|
||||
|
||||
# After (Gitea)
|
||||
@uconnect:registry=https://git.uscllc.com/api/packages/usc/npm/
|
||||
```
|
||||
|
||||
3. **Update CI pipeline** — replace `npm_token` secret with `gitea_token`:
|
||||
```yaml
|
||||
# Uses same token as Docker push — no extra secret needed
|
||||
echo "//GITEA_HOST/api/packages/ORG/npm/:_authToken=$$GITEA_TOKEN" > .npmrc
|
||||
```
|
||||
|
||||
4. **Re-publish existing packages** to Gitea registry:
|
||||
```bash
|
||||
# For each @scope/package
|
||||
npm publish -w @scope/package --registry https://GITEA_HOST/api/packages/ORG/npm/
|
||||
```
|
||||
|
||||
5. **Update consumer projects** — any project that `npm install`s from the old registry needs its `.npmrc` updated
|
||||
|
||||
6. **Remove Verdaccio infrastructure:**
|
||||
- Docker compose stack (`compose.verdaccio.yml`)
|
||||
- Authentik OAuth provider/blueprints
|
||||
- Verdaccio config files
|
||||
- DNS entry for `npm.uscllc.net` (eventually)
|
||||
|
||||
### What You Can Remove
|
||||
|
||||
| Component | Location | Purpose (was) |
|
||||
|-----------|----------|---------------|
|
||||
| Verdaccio compose | `compose.verdaccio.yml` | npm registry container |
|
||||
| Verdaccio config | `config/verdaccio/` | Server configuration |
|
||||
| Authentik blueprints | `config/authentik/blueprints/*/verdaccio-*` | OAuth integration |
|
||||
| Verdaccio scripts | `scripts/verdaccio/` | Blueprint application |
|
||||
| OIDC env vars | `.env` | `AUTHENTIK_VERDACCIO_*`, `VERDACCIO_OPENID_*` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "unauthorized: authentication required"
|
||||
- Verify `gitea_username` and `gitea_token` secrets are set in Woodpecker
|
||||
- Verify the token has `package:write` scope
|
||||
- Check the registry hostname in `kaniko_setup` matches the Gitea instance
|
||||
|
||||
### Kaniko build fails with "error building image"
|
||||
- Verify the Dockerfile path is correct relative to `--context`
|
||||
- Check that multi-stage builds don't reference stages that don't exist
|
||||
- Run `docker build` locally first to verify the Dockerfile works
|
||||
|
||||
### Package linking returns 404
|
||||
- Normal for recently pushed packages — the retry logic handles this
|
||||
- If persistent: verify the package name matches exactly (case-sensitive)
|
||||
- Check Gitea version is 1.24.0+ (package linking API requirement)
|
||||
|
||||
### Images not visible in Gitea Packages
|
||||
- Linking may have failed — check the `link-packages` step logs
|
||||
- Images are still usable via `docker pull` even without linking
|
||||
- Link manually: Gitea UI > Packages > Select package > Link to repository
|
||||
|
||||
### Pipeline runs Docker builds on pull requests
|
||||
- Verify `when` clause on Docker build steps restricts to `branch: [main, develop]`
|
||||
- Pull requests should only run quality gates, not build/push images
|
||||
101
guides/code-review.md
Normal file
101
guides/code-review.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Code Review Guide
|
||||
|
||||
## Review Checklist
|
||||
|
||||
### 1. Correctness
|
||||
- [ ] Code does what the issue/PR description says
|
||||
- [ ] Edge cases are handled
|
||||
- [ ] Error conditions are managed properly
|
||||
- [ ] No obvious bugs or logic errors
|
||||
|
||||
### 2. Security
|
||||
- [ ] No hardcoded secrets or credentials
|
||||
- [ ] Input validation at boundaries
|
||||
- [ ] SQL injection prevention (parameterized queries)
|
||||
- [ ] XSS prevention (output encoding)
|
||||
- [ ] Authentication/authorization checks present
|
||||
- [ ] Sensitive data not logged
|
||||
- [ ] Secrets follow Vault structure (see `docs/vault-secrets-structure.md`)
|
||||
|
||||
### 3. Testing
|
||||
- [ ] Tests exist for new functionality
|
||||
- [ ] Tests cover happy path AND error cases
|
||||
- [ ] Coverage meets 85% minimum
|
||||
- [ ] Tests are readable and maintainable
|
||||
- [ ] No flaky tests introduced
|
||||
|
||||
### 4. Code Quality
|
||||
- [ ] Follows Google Style Guide for the language
|
||||
- [ ] Functions are focused and reasonably sized
|
||||
- [ ] No unnecessary complexity
|
||||
- [ ] DRY - no significant duplication
|
||||
- [ ] Clear naming for variables and functions
|
||||
- [ ] No dead code or commented-out code
|
||||
|
||||
### 4a. TypeScript Strict Typing (see `typescript.md`)
|
||||
- [ ] **NO `any` types** — explicit types required everywhere
|
||||
- [ ] **NO lazy `unknown`** — only for error catches with immediate narrowing
|
||||
- [ ] **Explicit return types** on all exported/public functions
|
||||
- [ ] **Explicit parameter types** — never implicit any
|
||||
- [ ] **No type assertions** (`as Type`) — use type guards instead
|
||||
- [ ] **No non-null assertions** (`!`) — use proper null handling
|
||||
- [ ] **Interfaces for objects** — not inline types
|
||||
- [ ] **Discriminated unions** for variant types
|
||||
|
||||
### 5. Documentation
|
||||
- [ ] Complex logic has explanatory comments
|
||||
- [ ] Public APIs are documented
|
||||
- [ ] README updated if needed
|
||||
- [ ] Breaking changes noted
|
||||
|
||||
### 6. Performance
|
||||
- [ ] No obvious N+1 queries
|
||||
- [ ] No blocking operations in hot paths
|
||||
- [ ] Resource cleanup (connections, file handles)
|
||||
- [ ] Reasonable memory usage
|
||||
|
||||
### 7. Dependencies
|
||||
- [ ] No deprecated packages
|
||||
- [ ] No unnecessary new dependencies
|
||||
- [ ] Dependency versions pinned appropriately
|
||||
|
||||
## Review Process
|
||||
|
||||
### Getting Context
|
||||
```bash
|
||||
# List the issue being addressed
|
||||
~/.mosaic/rails/git/issue-list.sh -i {issue-number}
|
||||
|
||||
# View the changes
|
||||
git diff main...HEAD
|
||||
```
|
||||
|
||||
### Providing Feedback
|
||||
- Be specific: point to exact lines/files
|
||||
- Explain WHY something is problematic
|
||||
- Suggest alternatives when possible
|
||||
- Distinguish between blocking issues and suggestions
|
||||
- Be constructive, not critical of the person
|
||||
|
||||
### Feedback Categories
|
||||
- **Blocker**: Must fix before merge (security, bugs, test failures)
|
||||
- **Should Fix**: Important but not blocking (code quality, minor issues)
|
||||
- **Suggestion**: Optional improvements (style preferences, nice-to-haves)
|
||||
- **Question**: Seeking clarification
|
||||
|
||||
### Review Comment Format
|
||||
```
|
||||
[BLOCKER] Line 42: SQL injection vulnerability
|
||||
The user input is directly interpolated into the query.
|
||||
Use parameterized queries instead:
|
||||
`db.query("SELECT * FROM users WHERE id = ?", [userId])`
|
||||
|
||||
[SUGGESTION] Line 78: Consider extracting to helper
|
||||
This pattern appears in 3 places. A shared helper would reduce duplication.
|
||||
```
|
||||
|
||||
## After Review
|
||||
1. Update issue with review status
|
||||
2. If changes requested, assign back to author
|
||||
3. If approved, note approval in issue comments
|
||||
4. For merges, ensure CI passes first
|
||||
80
guides/frontend.md
Normal file
80
guides/frontend.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Frontend Development Guide
|
||||
|
||||
## Before Starting
|
||||
1. Check assigned issue in git repo: `~/.mosaic/rails/git/issue-list.sh -a @me`
|
||||
2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md`
|
||||
3. Review existing components and patterns in the codebase
|
||||
|
||||
## Development Standards
|
||||
|
||||
### Framework Conventions
|
||||
- Follow project's existing framework patterns (React, Vue, Svelte, etc.)
|
||||
- Use existing component library/design system if present
|
||||
- Maintain consistent file structure with existing code
|
||||
|
||||
### Styling
|
||||
- Use project's established styling approach (CSS modules, Tailwind, styled-components, etc.)
|
||||
- Follow existing naming conventions for CSS classes
|
||||
- Ensure responsive design unless explicitly single-platform
|
||||
|
||||
### State Management
|
||||
- Use project's existing state management solution
|
||||
- Keep component state local when possible
|
||||
- Document any new global state additions
|
||||
|
||||
### Accessibility
|
||||
- Include proper ARIA labels
|
||||
- Ensure keyboard navigation works
|
||||
- Test with screen reader considerations
|
||||
- Maintain color contrast ratios (WCAG 2.1 AA minimum)
|
||||
|
||||
## Testing Requirements (TDD)
|
||||
1. Write tests BEFORE implementation
|
||||
2. Minimum 85% coverage
|
||||
3. Test categories:
|
||||
- Unit tests for utility functions
|
||||
- Component tests for UI behavior
|
||||
- Integration tests for user flows
|
||||
|
||||
### Test Patterns
|
||||
```javascript
|
||||
// Component test example structure
|
||||
describe('ComponentName', () => {
|
||||
it('renders without crashing', () => {});
|
||||
it('handles user interaction correctly', () => {});
|
||||
it('displays error states appropriately', () => {});
|
||||
it('is accessible', () => {});
|
||||
});
|
||||
```
|
||||
|
||||
## Code Style
|
||||
- Follow Google JavaScript/TypeScript Style Guide
|
||||
- **TypeScript: Follow `~/.mosaic/guides/typescript.md` — MANDATORY**
|
||||
- Use ESLint/Prettier configuration from project
|
||||
- Prefer functional components over class components (React)
|
||||
- TypeScript strict mode is REQUIRED, not optional
|
||||
|
||||
### TypeScript Quick Rules (see typescript.md for full guide)
|
||||
- **NO `any`** — define explicit types always
|
||||
- **NO lazy `unknown`** — only for error catches and external data with validation
|
||||
- **Explicit return types** on all exported functions
|
||||
- **Explicit parameter types** always
|
||||
- **Interface for props** — never inline object types
|
||||
- **Event handlers** — use proper React event types
|
||||
|
||||
## Commit Format
|
||||
```
|
||||
feat(#123): Add user profile component
|
||||
|
||||
- Implement avatar display
|
||||
- Add edit mode toggle
|
||||
- Include form validation
|
||||
|
||||
Refs #123
|
||||
```
|
||||
|
||||
## Before Completing
|
||||
1. Run full test suite
|
||||
2. Verify build succeeds
|
||||
3. Update scratchpad with completion notes
|
||||
4. Reference issue in commit: `Fixes #N` or `Refs #N`
|
||||
165
guides/infrastructure.md
Normal file
165
guides/infrastructure.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Infrastructure & DevOps Guide
|
||||
|
||||
## Before Starting
|
||||
1. Check assigned issue: `~/.mosaic/rails/git/issue-list.sh -a @me`
|
||||
2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md`
|
||||
3. Review existing infrastructure configuration
|
||||
|
||||
## Vault Secrets Management
|
||||
|
||||
**CRITICAL**: Follow canonical Vault structure for ALL secrets.
|
||||
|
||||
### Structure
|
||||
```
|
||||
{mount}/{service}/{component}/{secret-name}
|
||||
|
||||
Examples:
|
||||
- secret-prod/postgres/database/app
|
||||
- secret-prod/redis/auth/default
|
||||
- secret-prod/authentik/admin/token
|
||||
```
|
||||
|
||||
### Environment Mounts
|
||||
- `secret-dev/` - Development environment
|
||||
- `secret-staging/` - Staging environment
|
||||
- `secret-prod/` - Production environment
|
||||
|
||||
### Standard Field Names
|
||||
- Credentials: `username`, `password`
|
||||
- Tokens: `token`
|
||||
- OAuth: `client_id`, `client_secret`
|
||||
- Connection strings: `url`, `host`, `port`
|
||||
|
||||
See `docs/vault-secrets-structure.md` for complete reference.
|
||||
|
||||
## Container Standards
|
||||
|
||||
### Dockerfile Best Practices
|
||||
```dockerfile
|
||||
# Use specific version tags
|
||||
FROM node:20-alpine
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -S app && adduser -S app -G app
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency files first (layer caching)
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy application code
|
||||
COPY --chown=app:app . .
|
||||
|
||||
# Switch to non-root user
|
||||
USER app
|
||||
|
||||
# Use exec form for CMD
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### Container Security
|
||||
- Use minimal base images (alpine, distroless)
|
||||
- Run as non-root user
|
||||
- Don't store secrets in images
|
||||
- Scan images for vulnerabilities
|
||||
- Pin dependency versions
|
||||
|
||||
## Kubernetes/Docker Compose
|
||||
|
||||
### Resource Limits
|
||||
Always set resource limits to prevent runaway containers:
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
```yaml
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 3
|
||||
```
|
||||
|
||||
## CI/CD Pipelines
|
||||
|
||||
### Pipeline Stages
|
||||
1. **Lint**: Code style and static analysis
|
||||
2. **Test**: Unit and integration tests
|
||||
3. **Build**: Compile and package
|
||||
4. **Scan**: Security and vulnerability scanning
|
||||
5. **Deploy**: Environment-specific deployment
|
||||
|
||||
### Pipeline Security
|
||||
- Use secrets management (not hardcoded)
|
||||
- Pin action/image versions
|
||||
- Implement approval gates for production
|
||||
- Audit pipeline access
|
||||
|
||||
## Monitoring & Logging
|
||||
|
||||
### Logging Standards
|
||||
- Use structured logging (JSON)
|
||||
- Include correlation IDs
|
||||
- Log at appropriate levels (ERROR, WARN, INFO, DEBUG)
|
||||
- Never log sensitive data
|
||||
|
||||
### Metrics to Collect
|
||||
- Request latency (p50, p95, p99)
|
||||
- Error rates
|
||||
- Resource utilization (CPU, memory)
|
||||
- Business metrics
|
||||
|
||||
### Alerting
|
||||
- Define SLOs (Service Level Objectives)
|
||||
- Alert on symptoms, not causes
|
||||
- Include runbook links in alerts
|
||||
- Avoid alert fatigue
|
||||
|
||||
## Testing Infrastructure
|
||||
|
||||
### Test Categories
|
||||
1. **Unit tests**: Terraform/Ansible logic
|
||||
2. **Integration tests**: Deployed resources work together
|
||||
3. **Smoke tests**: Critical paths after deployment
|
||||
4. **Chaos tests**: Failure mode validation
|
||||
|
||||
### Infrastructure Testing Tools
|
||||
- Terraform: `terraform validate`, `terraform plan`
|
||||
- Ansible: `ansible-lint`, molecule
|
||||
- Kubernetes: `kubectl dry-run`, kubeval
|
||||
- General: Terratest, ServerSpec
|
||||
|
||||
## Commit Format
|
||||
```
|
||||
chore(#67): Configure Redis cluster
|
||||
|
||||
- Add Redis StatefulSet with 3 replicas
|
||||
- Configure persistence with PVC
|
||||
- Add Vault secret for auth password
|
||||
|
||||
Refs #67
|
||||
```
|
||||
|
||||
## Before Completing
|
||||
1. Validate configuration syntax
|
||||
2. Run infrastructure tests
|
||||
3. Test in dev/staging first
|
||||
4. Document any manual steps required
|
||||
5. Update scratchpad and close issue
|
||||
126
guides/orchestrator-learnings.md
Normal file
126
guides/orchestrator-learnings.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Orchestrator Learnings (Universal)
|
||||
|
||||
> Cross-project heuristic adjustments based on observed variance data.
|
||||
>
|
||||
> **Note:** This file contains generic patterns only. Project-specific evidence is stored in each project's `docs/orchestrator-learnings.json`.
|
||||
|
||||
## Task Type Multipliers
|
||||
|
||||
Apply these multipliers to base estimates from `orchestrator.md`:
|
||||
|
||||
| Task Type | Base Estimate | Multiplier | Confidence | Samples | Last Updated |
|
||||
|-----------|---------------|------------|------------|---------|--------------|
|
||||
| STYLE_FIX | 3-5K | 0.64 | MEDIUM | n=1 | 2026-02-05 |
|
||||
| BULK_CLEANUP | file_count × 550 | 1.0 | MEDIUM | n=2 | 2026-02-05 |
|
||||
| GUARD_ADD | 5-8K | 1.0 | LOW | n=0 | - |
|
||||
| SECURITY_FIX | 8-12K | 2.5 | LOW | n=0 | - |
|
||||
| AUTH_ADD | 15-25K | 1.0 | HIGH | n=1 | 2026-02-05 |
|
||||
| REFACTOR | 10-15K | 1.0 | LOW | n=0 | - |
|
||||
| TEST_ADD | 15-25K | 1.0 | LOW | n=0 | - |
|
||||
| ERROR_HANDLING | 8-12K | 2.3 | MEDIUM | n=1 | 2026-02-05 |
|
||||
| CONFIG_DEFAULT_CHANGE | 5-10K | 1.8 | MEDIUM | n=1 | 2026-02-05 |
|
||||
| INPUT_VALIDATION | 5-8K | 1.7 | MEDIUM | n=1 | 2026-02-05 |
|
||||
|
||||
## Phase Factors
|
||||
|
||||
Apply to all estimates based on task position in milestone:
|
||||
|
||||
| Phase Position | Factor | Rationale |
|
||||
|----------------|--------|-----------|
|
||||
| Early (tasks 1-3) | 1.45 | Codebase learning overhead |
|
||||
| Mid (tasks 4-7) | 1.25 | Pattern recognition phase |
|
||||
| Late (tasks 8+) | 1.10 | Established patterns |
|
||||
|
||||
## Estimation Formula
|
||||
|
||||
```
|
||||
Final Estimate = Base Estimate × Type Multiplier × Phase Factor × TDD Overhead
|
||||
|
||||
Where:
|
||||
- Base Estimate: From orchestrator.md task type table
|
||||
- Type Multiplier: From table above (default 1.0)
|
||||
- Phase Factor: 1.45 / 1.25 / 1.10 based on position
|
||||
- TDD Overhead: 1.20 if tests required
|
||||
```
|
||||
|
||||
## Known Patterns
|
||||
|
||||
### BULK_CLEANUP
|
||||
|
||||
**Pattern:** Multi-file cleanup tasks are severely underestimated.
|
||||
|
||||
**Why:** Iterative testing across many files, cascading fixes, and debugging compound the effort.
|
||||
|
||||
**Observed:** +112% to +276% variance when using fixed estimates.
|
||||
|
||||
**Recommendation:** Use `file_count × 550` instead of fixed estimate.
|
||||
|
||||
### ERROR_HANDLING
|
||||
|
||||
**Pattern:** Error handling changes that modify type interfaces cascade through the codebase.
|
||||
|
||||
**Why:** Adding fields to result types requires updating all callers, error messages, and tests.
|
||||
|
||||
**Observed:** +131% variance.
|
||||
|
||||
**Multiplier:** 2.3x base estimate when type interfaces are modified.
|
||||
|
||||
### CONFIG_DEFAULT_CHANGE
|
||||
|
||||
**Pattern:** Config default changes require more test coverage than expected.
|
||||
|
||||
**Why:** Security-sensitive defaults need validation tests, warning tests, and edge case coverage.
|
||||
|
||||
**Observed:** +80% variance.
|
||||
|
||||
**Multiplier:** 1.8x when config changes need security validation.
|
||||
|
||||
### INPUT_VALIDATION
|
||||
|
||||
**Pattern:** Security input validation with allowlists is more complex than simple validation.
|
||||
|
||||
**Why:** Comprehensive allowlists (e.g., OAuth error codes), encoding requirements, and security tests add up.
|
||||
|
||||
**Observed:** +70% variance.
|
||||
|
||||
**Multiplier:** 1.7x when security allowlists are involved.
|
||||
|
||||
### STYLE_FIX
|
||||
|
||||
**Pattern:** Pure formatting fixes are faster than estimated when isolated.
|
||||
|
||||
**Observed:** -36% variance.
|
||||
|
||||
**Multiplier:** 0.64x for isolated style-only fixes.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change | Samples | Confidence |
|
||||
|------|--------|---------|------------|
|
||||
| 2026-02-05 | Added BULK_CLEANUP category | n=2 | MEDIUM |
|
||||
| 2026-02-05 | Added STYLE_FIX multiplier 0.64 | n=1 | MEDIUM |
|
||||
| 2026-02-05 | Confirmed AUTH_ADD heuristic accurate | n=1 | HIGH |
|
||||
| 2026-02-05 | Added ERROR_HANDLING multiplier 2.3x | n=1 | MEDIUM |
|
||||
| 2026-02-05 | Added CONFIG_DEFAULT_CHANGE multiplier 1.8x | n=1 | MEDIUM |
|
||||
| 2026-02-05 | Added INPUT_VALIDATION multiplier 1.7x | n=1 | MEDIUM |
|
||||
|
||||
## Update Protocol
|
||||
|
||||
**Graduated Autonomy:**
|
||||
|
||||
| Phase | Condition | Action |
|
||||
|-------|-----------|--------|
|
||||
| **Now** | All proposals | Human review required |
|
||||
| **After 3 milestones** | <30% change, n≥3 samples, HIGH confidence | Auto-update allowed |
|
||||
| **Mature** | All changes | Auto with notification, revert on regression |
|
||||
|
||||
**Validation Before Update:**
|
||||
1. Minimum 3 samples for same task type
|
||||
2. Standard deviation < 30% of mean
|
||||
3. Outliers (>2σ) excluded
|
||||
4. New formula must not increase variance on historical data
|
||||
|
||||
## Where to Find Project-Specific Data
|
||||
|
||||
- **Project learnings:** `<project>/docs/orchestrator-learnings.json`
|
||||
- **Cross-project metrics:** `jarvis-brain/data/orchestrator-metrics.json`
|
||||
866
guides/orchestrator.md
Normal file
866
guides/orchestrator.md
Normal file
@@ -0,0 +1,866 @@
|
||||
# Autonomous Orchestrator Guide
|
||||
|
||||
> Load this guide when orchestrating autonomous task completion across any project.
|
||||
|
||||
## Overview
|
||||
|
||||
The orchestrator **cold-starts** on any project with just a review report location and minimal kickstart. It autonomously:
|
||||
1. Parses review reports to extract findings
|
||||
2. Categorizes findings into phases by severity
|
||||
3. Estimates token usage per task
|
||||
4. Creates Gitea issues (phase-level)
|
||||
5. Bootstraps `docs/tasks.md` from scratch
|
||||
6. Coordinates completion using worker agents
|
||||
|
||||
**Key principle:** The orchestrator is the **sole writer** of `docs/tasks.md`. Worker agents execute tasks and report results — they never modify the tracking file.
|
||||
|
||||
---
|
||||
|
||||
## Orchestrator Boundaries (CRITICAL)
|
||||
|
||||
**The orchestrator NEVER:**
|
||||
- Edits source code directly (*.ts, *.tsx, *.js, *.py, etc.)
|
||||
- Runs quality gates itself (that's the worker's job)
|
||||
- Makes commits containing code changes
|
||||
- "Quickly fixes" something to save time — this is how drift starts
|
||||
|
||||
**The orchestrator ONLY:**
|
||||
- Reads/writes `docs/tasks.md`
|
||||
- Reads/writes `docs/orchestrator-learnings.json`
|
||||
- Spawns workers via the Task tool for ALL code changes
|
||||
- Parses worker JSON results
|
||||
- Commits task tracking updates (tasks.md, learnings)
|
||||
- Outputs status reports and handoff messages
|
||||
|
||||
**If you find yourself about to edit source code, STOP.**
|
||||
Spawn a worker instead. No exceptions. No "quick fixes."
|
||||
|
||||
**Worker Limits:**
|
||||
- Maximum **2 parallel workers** at any time
|
||||
- Wait for at least one worker to complete before spawning more
|
||||
- This optimizes token usage and reduces context pressure
|
||||
|
||||
---
|
||||
|
||||
## Bootstrap Templates
|
||||
|
||||
Use templates from `jarvis-brain/docs/templates/` to scaffold tracking files:
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export PROJECT="project-name"
|
||||
export MILESTONE="M1-Feature"
|
||||
export CURRENT_DATETIME=$(date -Iseconds)
|
||||
export TASK_PREFIX="PR-SEC"
|
||||
export PHASE_ISSUE="#1"
|
||||
export PHASE_BRANCH="fix/security"
|
||||
|
||||
# Copy templates
|
||||
TEMPLATES=~/src/jarvis-brain/docs/templates
|
||||
|
||||
# Create tasks.md (then populate with findings)
|
||||
envsubst < $TEMPLATES/orchestrator/tasks.md.template > docs/tasks.md
|
||||
|
||||
# Create learnings tracking
|
||||
envsubst < $TEMPLATES/orchestrator/orchestrator-learnings.json.template > docs/orchestrator-learnings.json
|
||||
|
||||
# Create review report structure (if doing new review)
|
||||
$TEMPLATES/reports/review-report-scaffold.sh codebase-review
|
||||
```
|
||||
|
||||
**Available templates:**
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `orchestrator/tasks.md.template` | Task tracking table with schema |
|
||||
| `orchestrator/orchestrator-learnings.json.template` | Variance tracking |
|
||||
| `orchestrator/phase-issue-body.md.template` | Gitea issue body |
|
||||
| `orchestrator/compaction-summary.md.template` | 60% checkpoint format |
|
||||
| `reports/review-report-scaffold.sh` | Creates report directory |
|
||||
| `scratchpad.md.template` | Per-task working document |
|
||||
|
||||
See `jarvis-brain/docs/templates/README.md` for full documentation.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Bootstrap
|
||||
|
||||
### Step 1: Parse Review Reports
|
||||
|
||||
Review reports typically follow this structure:
|
||||
```
|
||||
docs/reports/{report-name}/
|
||||
├── 00-executive-summary.md # Start here - overview and counts
|
||||
├── 01-security-review.md # Security findings with IDs like SEC-*
|
||||
├── 02-code-quality-review.md # Code quality findings like CQ-*
|
||||
├── 03-qa-test-coverage.md # Test coverage gaps like TEST-*
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Extract findings by looking for:**
|
||||
- Finding IDs (e.g., `SEC-API-1`, `CQ-WEB-3`, `TEST-001`)
|
||||
- Severity labels: Critical, High, Medium, Low
|
||||
- Affected files/components (use for `repo` column)
|
||||
- Specific line numbers or code patterns
|
||||
|
||||
**Parse each finding into:**
|
||||
```
|
||||
{
|
||||
id: "SEC-API-1",
|
||||
severity: "critical",
|
||||
title: "Brief description",
|
||||
component: "api", // For repo column
|
||||
file: "path/to/file.ts", // Reference for worker
|
||||
lines: "45-67" // Specific location
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Categorize into Phases
|
||||
|
||||
Map severity to phases:
|
||||
|
||||
| Severity | Phase | Focus | Branch Pattern |
|
||||
|----------|-------|-------|----------------|
|
||||
| Critical | 1 | Security vulnerabilities, data exposure | `fix/security` |
|
||||
| High | 2 | Security hardening, auth gaps | `fix/security` |
|
||||
| Medium | 3 | Code quality, performance, bugs | `fix/code-quality` |
|
||||
| Low | 4 | Tests, documentation, cleanup | `fix/test-coverage` |
|
||||
|
||||
**Within each phase, order tasks by:**
|
||||
1. Blockers first (tasks that unblock others)
|
||||
2. Same-file tasks grouped together
|
||||
3. Simpler fixes before complex ones
|
||||
|
||||
### Step 3: Estimate Token Usage
|
||||
|
||||
Use these heuristics based on task type:
|
||||
|
||||
| Task Type | Estimate | Examples |
|
||||
|-----------|----------|----------|
|
||||
| Single-line fix | 3-5K | Typo, wrong operator, missing null check |
|
||||
| Add guard/validation | 5-8K | Add auth decorator, input validation |
|
||||
| Fix error handling | 8-12K | Proper try/catch, error propagation |
|
||||
| Refactor pattern | 10-15K | Replace KEYS with SCAN, fix memory leak |
|
||||
| Add new functionality | 15-25K | New service method, new component |
|
||||
| Write tests | 15-25K | Unit tests for untested service |
|
||||
| Complex refactor | 25-40K | Architectural change, multi-file refactor |
|
||||
|
||||
**Adjust estimates based on:**
|
||||
- Number of files affected (+5K per additional file)
|
||||
- Test requirements (+5-10K if tests needed)
|
||||
- Documentation needs (+2-3K if docs needed)
|
||||
|
||||
### Step 4: Determine Dependencies
|
||||
|
||||
**Automatic dependency rules:**
|
||||
1. All tasks in Phase N depend on the Phase N-1 verification task
|
||||
2. Tasks touching the same file should be sequential (earlier blocks later)
|
||||
3. Auth/security foundation tasks block tasks that rely on them
|
||||
4. Each phase ends with a verification task that depends on all phase tasks
|
||||
|
||||
**Create verification tasks:**
|
||||
- `{PREFIX}-SEC-{LAST}`: Phase 1 verification (run security tests)
|
||||
- `{PREFIX}-HIGH-{LAST}`: Phase 2 verification
|
||||
- `{PREFIX}-CQ-{LAST}`: Phase 3 verification
|
||||
- `{PREFIX}-TEST-{LAST}`: Phase 4 verification (final quality gates)
|
||||
|
||||
### Step 5: Create Gitea Issues (Phase-Level)
|
||||
|
||||
Create ONE issue per phase using git scripts:
|
||||
|
||||
```bash
|
||||
~/.mosaic/rails/git/issue-create.sh \
|
||||
-t "Phase 1: Critical Security Fixes" \
|
||||
-b "$(cat <<'EOF'
|
||||
## Findings
|
||||
|
||||
- SEC-API-1: Description
|
||||
- SEC-WEB-2: Description
|
||||
- SEC-ORCH-1: Description
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All critical findings remediated
|
||||
- [ ] Quality gates passing
|
||||
- [ ] No new regressions
|
||||
EOF
|
||||
)" \
|
||||
-l "security,critical" \
|
||||
-m "{milestone-name}"
|
||||
```
|
||||
|
||||
**Capture issue numbers** — you'll link tasks to these.
|
||||
|
||||
### Step 6: Create docs/tasks.md
|
||||
|
||||
Create the file with this exact schema:
|
||||
|
||||
```markdown
|
||||
# Tasks
|
||||
|
||||
| id | status | description | issue | repo | branch | depends_on | blocks | agent | started_at | completed_at | estimate | used |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| {PREFIX}-SEC-001 | not-started | SEC-API-1: Brief description | #{N} | api | fix/security | | {PREFIX}-SEC-002 | | | | 8K | |
|
||||
```
|
||||
|
||||
**Column definitions:**
|
||||
|
||||
| Column | Format | Purpose |
|
||||
|--------|--------|---------|
|
||||
| `id` | `{PREFIX}-{CAT}-{NNN}` | Unique task ID (e.g., MS-SEC-001) |
|
||||
| `status` | `not-started` \| `in-progress` \| `done` \| `failed` | Current state |
|
||||
| `description` | `{FindingID}: Brief summary` | What to fix |
|
||||
| `issue` | `#NNN` | Gitea issue (phase-level, all tasks in phase share) |
|
||||
| `repo` | Workspace name | `api`, `web`, `orchestrator`, etc. |
|
||||
| `branch` | Branch name | `fix/security`, `fix/code-quality`, etc. |
|
||||
| `depends_on` | Comma-separated IDs | Must complete first |
|
||||
| `blocks` | Comma-separated IDs | Tasks waiting on this |
|
||||
| `agent` | Agent identifier | Assigned worker (fill when claiming) |
|
||||
| `started_at` | ISO 8601 | When work began |
|
||||
| `completed_at` | ISO 8601 | When work finished |
|
||||
| `estimate` | `5K`, `15K`, etc. | Predicted token usage |
|
||||
| `used` | `4.2K`, `12.8K`, etc. | Actual usage (fill on completion) |
|
||||
|
||||
**Category prefixes:**
|
||||
- `SEC` — Security (Phase 1-2)
|
||||
- `HIGH` — High priority (Phase 2)
|
||||
- `CQ` — Code quality (Phase 3)
|
||||
- `TEST` — Test coverage (Phase 4)
|
||||
- `PERF` — Performance (Phase 3)
|
||||
|
||||
### Step 7: Commit Bootstrap
|
||||
|
||||
```bash
|
||||
git add docs/tasks.md
|
||||
git commit -m "chore(orchestrator): Bootstrap tasks.md from review report
|
||||
|
||||
Parsed {N} findings into {M} tasks across {P} phases.
|
||||
Estimated total: {X}K tokens."
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Execution Loop
|
||||
|
||||
```
|
||||
1. git pull --rebase
|
||||
2. Read docs/tasks.md
|
||||
3. Find next task: status=not-started AND all depends_on are done
|
||||
4. If no task available:
|
||||
- All done? → Report success, run final retrospective, STOP
|
||||
- Some blocked? → Report deadlock, STOP
|
||||
5. Update tasks.md: status=in-progress, agent={identifier}, started_at={now}
|
||||
6. Spawn worker agent (Task tool) with task details
|
||||
7. Wait for worker completion
|
||||
8. Parse worker result (JSON)
|
||||
9. **Variance check**: Calculate (actual - estimate) / estimate × 100
|
||||
- If |variance| > 50%: Capture learning (see Learning & Retrospective)
|
||||
- If |variance| > 100%: Flag as CRITICAL — review task classification
|
||||
10. **Post-Coding Review** (see Phase 2b below)
|
||||
11. Update tasks.md: status=done/failed/needs-qa, completed_at={now}, used={actual}
|
||||
12. **Cleanup reports**: Remove processed report files for completed task
|
||||
```bash
|
||||
# Find and remove reports matching the finding ID
|
||||
find docs/reports/qa-automation/pending/ -name "*{finding_id}*" -delete 2>/dev/null || true
|
||||
# If task failed, move reports to escalated/ instead
|
||||
```
|
||||
13. Commit + push: git add docs/tasks.md .gitignore && git commit && git push
|
||||
14. If phase verification task: Run phase retrospective, clean up all phase reports
|
||||
15. Check context usage
|
||||
16. If >= 55%: Output COMPACTION REQUIRED checkpoint, STOP, wait for user
|
||||
17. If < 55%: Go to step 1
|
||||
18. After user runs /compact and says "continue": Go to step 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2b: Post-Coding Review (MANDATORY)
|
||||
|
||||
**CRITICAL:** After any worker completes a task that modifies source code, the orchestrator MUST run an independent review before marking the task as done. This catches bugs, security issues, and regressions that the worker missed.
|
||||
|
||||
### When to Review
|
||||
|
||||
Run review when the worker's result includes code changes (commits). Skip for tasks that only modify docs, config, or tracking files.
|
||||
|
||||
### Step 1: Run Codex Review (Primary)
|
||||
|
||||
```bash
|
||||
# Navigate to the project directory
|
||||
cd {project_path}
|
||||
|
||||
# Code quality review
|
||||
~/.mosaic/rails/codex/codex-code-review.sh -b {base_branch} -o /tmp/review-{task_id}.json
|
||||
|
||||
# Security review
|
||||
~/.mosaic/rails/codex/codex-security-review.sh -b {base_branch} -o /tmp/security-{task_id}.json
|
||||
```
|
||||
|
||||
### Step 2: Parse Review Results
|
||||
|
||||
```bash
|
||||
# Check code review
|
||||
CODE_BLOCKERS=$(jq '.stats.blockers // 0' /tmp/review-{task_id}.json)
|
||||
CODE_VERDICT=$(jq -r '.verdict // "comment"' /tmp/review-{task_id}.json)
|
||||
|
||||
# Check security review
|
||||
SEC_CRITICAL=$(jq '.stats.critical // 0' /tmp/security-{task_id}.json)
|
||||
SEC_HIGH=$(jq '.stats.high // 0' /tmp/security-{task_id}.json)
|
||||
```
|
||||
|
||||
### Step 3: Decision Tree
|
||||
|
||||
```
|
||||
IF Codex is unavailable (command not found, auth failure, API error):
|
||||
→ Use fallback review (Step 4)
|
||||
|
||||
IF CODE_BLOCKERS > 0 OR SEC_CRITICAL > 0 OR SEC_HIGH > 0:
|
||||
→ Mark task as "needs-qa" in tasks.md
|
||||
→ Create a remediation task:
|
||||
- ID: {task_id}-QA
|
||||
- Description: Fix findings from review (list specific issues)
|
||||
- depends_on: (none — it's a follow-up, not a blocker)
|
||||
- Notes: Include finding titles and file locations
|
||||
→ Continue to next task (remediation task will be picked up in order)
|
||||
|
||||
IF CODE_VERDICT == "request-changes" (but no blockers):
|
||||
→ Log should-fix findings in task notes
|
||||
→ Mark task as done (non-blocking suggestions)
|
||||
→ Consider creating a tech-debt issue for significant suggestions
|
||||
|
||||
IF CODE_VERDICT == "approve" AND SEC_CRITICAL == 0 AND SEC_HIGH == 0:
|
||||
→ Mark task as done
|
||||
→ Log: "Review passed — no issues found"
|
||||
```
|
||||
|
||||
### Step 4: Fallback Review (When Codex is Unavailable)
|
||||
|
||||
If the `codex` CLI is not installed or authentication fails, use Claude's built-in review capabilities:
|
||||
|
||||
```markdown
|
||||
## Fallback: Spawn a Review Agent
|
||||
|
||||
Use the Task tool to spawn a review subagent:
|
||||
|
||||
Prompt:
|
||||
---
|
||||
## Independent Code Review
|
||||
|
||||
Review the code changes on branch {branch} against {base_branch}.
|
||||
|
||||
1. Run: `git diff {base_branch}...HEAD`
|
||||
2. Review for:
|
||||
- Correctness (bugs, logic errors, edge cases)
|
||||
- Security (OWASP Top 10, secrets, injection)
|
||||
- Testing (coverage, quality)
|
||||
- Code quality (complexity, duplication)
|
||||
3. Reference: ~/.mosaic/guides/code-review.md
|
||||
|
||||
Report findings as JSON:
|
||||
```json
|
||||
{
|
||||
"verdict": "approve|request-changes",
|
||||
"blockers": 0,
|
||||
"critical_security": 0,
|
||||
"findings": [
|
||||
{"severity": "blocker|should-fix|suggestion", "title": "...", "file": "...", "description": "..."}
|
||||
]
|
||||
}
|
||||
```
|
||||
---
|
||||
```
|
||||
|
||||
### Review Timing Guidelines
|
||||
|
||||
| Task Type | Review Required? |
|
||||
|-----------|-----------------|
|
||||
| Source code changes (*.ts, *.py, etc.) | **YES — always** |
|
||||
| Configuration changes (*.yml, *.toml) | YES — security review only |
|
||||
| Documentation changes (*.md) | No |
|
||||
| Task tracking updates (tasks.md) | No |
|
||||
| Test-only changes | YES — code review only |
|
||||
|
||||
### Logging Review Results
|
||||
|
||||
In the task notes column of tasks.md, append review results:
|
||||
|
||||
```
|
||||
Review: approve (0 blockers, 0 critical) | Codex 0.98.0
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```
|
||||
Review: needs-qa (1 blocker, 2 high) → QA task {task_id}-QA created
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Worker Prompt Template
|
||||
|
||||
Construct this from the task row and pass to worker via Task tool:
|
||||
|
||||
````markdown
|
||||
## Task Assignment: {id}
|
||||
|
||||
**Description:** {description}
|
||||
**Repository:** {project_path}/apps/{repo}
|
||||
**Branch:** {branch}
|
||||
|
||||
**Reference:** See `docs/reports/` for detailed finding description. Search for the finding ID.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Checkout branch: `git checkout {branch} || git checkout -b {branch} develop && git pull`
|
||||
2. Read the finding details from the report
|
||||
3. Implement the fix following existing code patterns
|
||||
4. Run quality gates (ALL must pass — zero lint errors, zero type errors, all tests green):
|
||||
```bash
|
||||
{quality_gates_command}
|
||||
```
|
||||
**MANDATORY:** This ALWAYS includes linting. If the project has a linter configured
|
||||
(ESLint, Biome, ruff, etc.), you MUST run it and fix ALL violations in files you touched.
|
||||
Do NOT leave lint warnings or errors for someone else to clean up.
|
||||
5. If gates fail: Fix and retry. Do NOT report success with failures.
|
||||
6. Commit: `git commit -m "fix({finding_id}): brief description"`
|
||||
7. Push: `git push origin {branch}`
|
||||
8. Report result as JSON (see format below)
|
||||
|
||||
## Git Scripts
|
||||
|
||||
For issue/PR/milestone operations, use scripts (NOT raw tea/gh):
|
||||
- `~/.mosaic/rails/git/issue-view.sh -i {N}`
|
||||
- `~/.mosaic/rails/git/pr-create.sh -t "Title" -b "Desc" -B develop`
|
||||
|
||||
Standard git commands (pull, commit, push, checkout) are fine.
|
||||
|
||||
## Result Format (MANDATORY)
|
||||
|
||||
End your response with this JSON block:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "{id}",
|
||||
"status": "success|failed",
|
||||
"used": "5.2K",
|
||||
"commit_sha": "abc123",
|
||||
"notes": "Brief summary of what was done"
|
||||
}
|
||||
```
|
||||
|
||||
## Post-Coding Review
|
||||
|
||||
After you complete and push your changes, the orchestrator will independently
|
||||
review your code using Codex (or a fallback review agent). If the review finds
|
||||
blockers or critical security issues, a follow-up remediation task will be
|
||||
created. You do NOT need to run the review yourself — the orchestrator handles it.
|
||||
|
||||
## Rules
|
||||
|
||||
- DO NOT modify docs/tasks.md
|
||||
- DO NOT claim other tasks
|
||||
- Complete this single task, report results, done
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Context Threshold Protocol (Orchestrator Replacement)
|
||||
|
||||
**Threshold:** 55-60% context usage
|
||||
|
||||
**Why replacement, not compaction?**
|
||||
- Compaction causes **protocol drift** — agent "remembers" gist but loses specifics
|
||||
- Post-compaction agents may violate core rules (e.g., letting workers modify tasks.md)
|
||||
- Fresh orchestrator has **100% protocol fidelity**
|
||||
- All state lives in `docs/tasks.md` — the orchestrator is **stateless and replaceable**
|
||||
|
||||
**At threshold (55-60%):**
|
||||
|
||||
1. Complete current task
|
||||
2. Persist all state:
|
||||
- Update docs/tasks.md with all progress
|
||||
- Update docs/orchestrator-learnings.json with variances
|
||||
- Commit and push both files
|
||||
3. Output **ORCHESTRATOR HANDOFF** message with ready-to-use takeover kickstart
|
||||
4. **STOP COMPLETELY** — do not continue working
|
||||
|
||||
**Handoff message format:**
|
||||
|
||||
```
|
||||
---
|
||||
⚠️ ORCHESTRATOR HANDOFF REQUIRED
|
||||
|
||||
Context: {X}% — Replacement recommended to prevent drift
|
||||
|
||||
Progress: {completed}/{total} tasks ({percentage}%)
|
||||
Current phase: Phase {N} ({phase_name})
|
||||
|
||||
State persisted:
|
||||
- docs/tasks.md ✓
|
||||
- docs/orchestrator-learnings.json ✓
|
||||
|
||||
## Takeover Kickstart
|
||||
|
||||
Copy and paste this to spawn a fresh orchestrator:
|
||||
|
||||
---
|
||||
## Continuation Mission
|
||||
|
||||
Continue {mission_description} from existing state.
|
||||
|
||||
## Setup
|
||||
- Project: {project_path}
|
||||
- State: docs/tasks.md (already populated)
|
||||
- Protocol: docs/claude/orchestrator.md
|
||||
- Quality gates: {quality_gates_command}
|
||||
|
||||
## Resume Point
|
||||
- Next task: {task_id}
|
||||
- Phase: {current_phase}
|
||||
- Progress: {completed}/{total} tasks ({percentage}%)
|
||||
|
||||
## Instructions
|
||||
1. Read docs/claude/orchestrator.md for protocol
|
||||
2. Read docs/tasks.md to understand current state
|
||||
3. Continue execution from task {task_id}
|
||||
4. Follow Two-Phase Completion Protocol
|
||||
5. You are the SOLE writer of docs/tasks.md
|
||||
---
|
||||
|
||||
STOP: Terminate this session and spawn fresh orchestrator with the kickstart above.
|
||||
---
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Do NOT attempt to compact yourself — compaction causes drift
|
||||
- Do NOT continue past 60%
|
||||
- Do NOT claim you can "just continue" — protocol drift is real
|
||||
- STOP means STOP — the user (Coordinator) will spawn your replacement
|
||||
- Include ALL context needed for the replacement in the takeover kickstart
|
||||
|
||||
---
|
||||
|
||||
## Two-Phase Completion Protocol
|
||||
|
||||
Each major phase uses a two-phase approach to maximize completion while managing diminishing returns.
|
||||
|
||||
### Bulk Phase (Target: 90%)
|
||||
|
||||
- Focus on tractable errors
|
||||
- Parallelize where possible
|
||||
- When 90% reached, transition to Polish (do NOT declare success)
|
||||
|
||||
### Polish Phase (Target: 100%)
|
||||
|
||||
1. **Inventory:** List all remaining errors with file:line
|
||||
2. **Categorize:**
|
||||
| Category | Criteria | Action |
|
||||
|----------|----------|--------|
|
||||
| Quick-win | <5 min, straightforward | Fix immediately |
|
||||
| Medium | 5-30 min, clear path | Fix in order |
|
||||
| Hard | >30 min or uncertain | Attempt 15 min, then document |
|
||||
| Architectural | Requires design change | Document and defer |
|
||||
|
||||
3. **Work priority:** Quick-win → Medium → Hard
|
||||
4. **Document deferrals** in `docs/deferred-errors.md`:
|
||||
```markdown
|
||||
## {PREFIX}-XXX: [Error description]
|
||||
- File: path/to/file.ts:123
|
||||
- Error: [exact error message]
|
||||
- Category: Hard | Architectural | Framework Limitation
|
||||
- Reason: [why this is non-trivial]
|
||||
- Suggested approach: [how to fix in future]
|
||||
- Risk: Low | Medium | High
|
||||
```
|
||||
|
||||
5. **Phase complete when:**
|
||||
- All Quick-win/Medium fixed
|
||||
- All Hard attempted (fixed or documented)
|
||||
- Architectural items documented with justification
|
||||
|
||||
### Phase Boundary Rule
|
||||
|
||||
Do NOT proceed to the next major phase until the current phase reaches Polish completion:
|
||||
|
||||
```
|
||||
✅ Phase 2 Bulk: 91%
|
||||
✅ Phase 2 Polish: 118 errors triaged
|
||||
- 40 medium → fixed
|
||||
- 78 low → EACH documented with rationale
|
||||
✅ Phase 2 Complete: Created docs/deferred-errors.md
|
||||
→ NOW proceed to Phase 3
|
||||
|
||||
❌ WRONG: Phase 2 at 91%, "low priority acceptable", starting Phase 3
|
||||
```
|
||||
|
||||
### Reporting
|
||||
|
||||
When transitioning from Bulk to Polish:
|
||||
```
|
||||
Phase X Bulk Complete: {N}% ({fixed}/{total})
|
||||
Entering Polish Phase: {remaining} errors to triage
|
||||
```
|
||||
|
||||
When Polish Phase complete:
|
||||
```
|
||||
Phase X Complete: {final_pct}% ({fixed}/{total})
|
||||
- Quick-wins: {n} fixed
|
||||
- Medium: {n} fixed
|
||||
- Hard: {n} fixed, {n} documented
|
||||
- Framework limitations: {n} documented
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Learning & Retrospective
|
||||
|
||||
Orchestrators capture learnings to improve future estimation accuracy.
|
||||
|
||||
### Variance Thresholds
|
||||
|
||||
| Variance | Action |
|
||||
|----------|--------|
|
||||
| 0-30% | Log only (acceptable) |
|
||||
| 30-50% | Flag for review |
|
||||
| 50-100% | Capture learning to `docs/orchestrator-learnings.json` |
|
||||
| >100% | CRITICAL — review task classification, possible mismatch |
|
||||
|
||||
### Task Type Classification
|
||||
|
||||
Classify tasks by description keywords for pattern analysis:
|
||||
|
||||
| Type | Keywords | Base Estimate |
|
||||
|------|----------|---------------|
|
||||
| STYLE_FIX | "formatting", "prettier", "lint" | 3-5K |
|
||||
| BULK_CLEANUP | "unused", "warnings", "~N files" | file_count × 550 |
|
||||
| GUARD_ADD | "add guard", "decorator", "validation" | 5-8K |
|
||||
| SECURITY_FIX | "sanitize", "injection", "XSS" | 8-12K × 2.5 |
|
||||
| AUTH_ADD | "authentication", "auth" | 15-25K |
|
||||
| REFACTOR | "refactor", "replace", "migrate" | 10-15K |
|
||||
| TEST_ADD | "add tests", "coverage" | 15-25K |
|
||||
|
||||
### Capture Learning
|
||||
|
||||
When |variance| > 50%, append to `docs/orchestrator-learnings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "UC-CLEAN-003",
|
||||
"task_type": "BULK_CLEANUP",
|
||||
"estimate_k": 30,
|
||||
"actual_k": 112.8,
|
||||
"variance_pct": 276,
|
||||
"characteristics": {
|
||||
"file_count": 200,
|
||||
"keywords": ["object injection", "type guards"]
|
||||
},
|
||||
"analysis": "Multi-file type guards severely underestimated",
|
||||
"captured_at": "2026-02-05T19:45:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Retrospective Triggers
|
||||
|
||||
| Trigger | Action |
|
||||
|---------|--------|
|
||||
| Phase verification task | Analyze phase variance, summarize patterns |
|
||||
| 60% compaction | Persist learnings buffer, include in summary |
|
||||
| Milestone complete | Full retrospective, generate heuristic proposals |
|
||||
|
||||
### Enhanced Compaction Summary
|
||||
|
||||
Include learnings in compaction output:
|
||||
|
||||
```
|
||||
Session Summary (Compacting at 60%):
|
||||
|
||||
Completed: MS-SEC-001 (15K→0.3K, -98%), MS-SEC-002 (8K→12K, +50%)
|
||||
Quality: All gates passing
|
||||
|
||||
Learnings Captured:
|
||||
- MS-SEC-001: -98% variance — AUTH_ADD may need SKIP_IF_EXISTS category
|
||||
- MS-SEC-002: +50% variance — XSS sanitization more complex than expected
|
||||
|
||||
Remaining: MS-SEC-004 (ready), MS-SEC-005 through MS-SEC-010
|
||||
Next: MS-SEC-004
|
||||
```
|
||||
|
||||
### Cross-Project Learnings
|
||||
|
||||
Universal heuristics are maintained in `~/.mosaic/guides/orchestrator-learnings.md`.
|
||||
After completing a milestone, review variance patterns and propose updates to the universal guide.
|
||||
|
||||
---
|
||||
|
||||
## Report Cleanup
|
||||
|
||||
QA automation generates report files in `docs/reports/qa-automation/pending/`. These must be cleaned up to prevent accumulation.
|
||||
|
||||
**Directory structure:**
|
||||
```
|
||||
docs/reports/qa-automation/
|
||||
├── pending/ # Reports awaiting processing
|
||||
└── escalated/ # Reports for failed tasks (manual review needed)
|
||||
```
|
||||
|
||||
**Gitignore:** Add this to project `.gitignore`:
|
||||
```
|
||||
# Orchestrator reports (generated by QA automation, cleaned up after processing)
|
||||
docs/reports/qa-automation/
|
||||
```
|
||||
|
||||
**Cleanup timing:**
|
||||
| Event | Action |
|
||||
|-------|--------|
|
||||
| Task success | Delete matching reports from `pending/` |
|
||||
| Task failed | Move reports to `escalated/` for investigation |
|
||||
| Phase verification | Clean up all `pending/` reports for that phase |
|
||||
| Milestone complete | Archive or delete entire `escalated/` directory |
|
||||
|
||||
**Cleanup commands:**
|
||||
```bash
|
||||
# After successful task (finding ID pattern, e.g., SEC-API-1)
|
||||
find docs/reports/qa-automation/pending/ -name "*relevant-file-pattern*" -delete
|
||||
|
||||
# After phase verification - clean all pending
|
||||
rm -rf docs/reports/qa-automation/pending/*
|
||||
|
||||
# Move failed task reports to escalated
|
||||
mv docs/reports/qa-automation/pending/*failing-file* docs/reports/qa-automation/escalated/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Quality gates fail:**
|
||||
1. Worker should retry up to 2 times
|
||||
2. If still failing, worker reports `failed` with error details
|
||||
3. Orchestrator updates tasks.md: keep `in-progress`, add notes
|
||||
4. Orchestrator may re-spawn with error context, or mark `failed` and continue
|
||||
5. If failed task blocks others: Report deadlock, STOP
|
||||
|
||||
**Worker reports blocker:**
|
||||
1. Update tasks.md with blocker notes
|
||||
2. Skip to next unblocked task if possible
|
||||
3. If all remaining tasks blocked: Report blockers, STOP
|
||||
|
||||
**Git push conflict:**
|
||||
1. `git pull --rebase`
|
||||
2. If auto-resolves: push again
|
||||
3. If conflict on tasks.md: Report, STOP (human resolves)
|
||||
|
||||
---
|
||||
|
||||
## Stopping Criteria
|
||||
|
||||
**ONLY stop if:**
|
||||
1. All tasks in docs/tasks.md are `done`
|
||||
2. Critical blocker preventing progress (document and alert)
|
||||
3. Context usage >= 55% — output COMPACTION REQUIRED checkpoint and wait
|
||||
4. Absolute context limit reached AND cannot compact further
|
||||
|
||||
**DO NOT stop to ask "should I continue?"** — the answer is always YES.
|
||||
**DO stop at 55-60%** — output the compaction checkpoint and wait for user to run `/compact`.
|
||||
|
||||
---
|
||||
|
||||
## Sprint Completion Protocol
|
||||
|
||||
When all tasks in `docs/tasks.md` are `done` (or triaged as `deferred`), archive the sprint artifacts before stopping. This preserves them for post-mortems, variance calibration, and historical reference.
|
||||
|
||||
### Archive Steps
|
||||
|
||||
1. **Create archive directory** (if it doesn't exist):
|
||||
```bash
|
||||
mkdir -p docs/tasks/
|
||||
```
|
||||
|
||||
2. **Move tasks.md to archive:**
|
||||
```bash
|
||||
mv docs/tasks.md docs/tasks/{milestone-name}-tasks.md
|
||||
```
|
||||
Example: `docs/tasks/M6-AgentOrchestration-Fixes-tasks.md`
|
||||
|
||||
3. **Move learnings to archive:**
|
||||
```bash
|
||||
mv docs/orchestrator-learnings.json docs/tasks/{milestone-name}-learnings.json
|
||||
```
|
||||
|
||||
4. **Commit the archive:**
|
||||
```bash
|
||||
git add docs/tasks/
|
||||
git rm docs/tasks.md docs/orchestrator-learnings.json 2>/dev/null || true
|
||||
git commit -m "chore(orchestrator): Archive {milestone-name} sprint artifacts
|
||||
|
||||
{completed}/{total} tasks completed, {deferred} deferred.
|
||||
Archived to docs/tasks/ for post-mortem reference."
|
||||
git push
|
||||
```
|
||||
|
||||
5. **Run final retrospective** — review variance patterns and propose updates to estimation heuristics.
|
||||
|
||||
### Recovery
|
||||
|
||||
If an orchestrator starts and `docs/tasks.md` does not exist, check `docs/tasks/` for the most recent archive:
|
||||
|
||||
```bash
|
||||
ls -t docs/tasks/*-tasks.md 2>/dev/null | head -1
|
||||
```
|
||||
|
||||
If found, this may indicate another session archived the file. The orchestrator should:
|
||||
1. Report what it found in `docs/tasks/`
|
||||
2. Ask whether to resume from the archived file or bootstrap fresh
|
||||
3. If resuming: copy the archive back to `docs/tasks.md` and continue
|
||||
|
||||
### Retention Policy
|
||||
|
||||
Keep all archived sprints indefinitely. They are small text files and valuable for:
|
||||
- Post-mortem analysis
|
||||
- Estimation variance calibration across milestones
|
||||
- Understanding what was deferred and why
|
||||
- Onboarding new orchestrators to project history
|
||||
|
||||
---
|
||||
|
||||
## Kickstart Message Format
|
||||
|
||||
The kickstart should be **minimal** — the orchestrator figures out the rest:
|
||||
|
||||
```markdown
|
||||
## Mission
|
||||
Remediate findings from the codebase review.
|
||||
|
||||
## Setup
|
||||
- Project: /path/to/project
|
||||
- Review: docs/reports/{report-name}/
|
||||
- Quality gates: {command}
|
||||
- Milestone: {milestone-name} (for issue creation)
|
||||
- Task prefix: {PREFIX} (e.g., MS, UC)
|
||||
|
||||
## Protocol
|
||||
Read ~/.mosaic/guides/orchestrator.md for full instructions.
|
||||
|
||||
## Start
|
||||
Bootstrap from the review report, then execute until complete.
|
||||
```
|
||||
|
||||
**The orchestrator will:**
|
||||
1. Read this guide
|
||||
2. Parse the review reports
|
||||
3. Determine phases, estimates, dependencies
|
||||
4. Create issues and tasks.md
|
||||
5. Execute until done or blocked
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Phase | Action |
|
||||
|-------|--------|
|
||||
| Bootstrap | Parse reports → Categorize → Estimate → Create issues → Create tasks.md |
|
||||
| Execute | Loop: claim → spawn worker → update → commit |
|
||||
| Compact | At 60%: summarize, clear history, continue |
|
||||
| Stop | Queue empty, blocker, or context limit |
|
||||
|
||||
**Orchestrator owns tasks.md. Workers execute and report. Single writer eliminates conflicts.**
|
||||
202
guides/qa-testing.md
Normal file
202
guides/qa-testing.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# QA & Testing Guide
|
||||
|
||||
## Before Starting
|
||||
1. Check assigned issue: `~/.mosaic/rails/git/issue-list.sh -a @me`
|
||||
2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md`
|
||||
3. Review existing test structure and patterns
|
||||
|
||||
## Test-Driven Development (TDD) Process
|
||||
|
||||
### The TDD Cycle
|
||||
1. **Red**: Write a failing test first
|
||||
2. **Green**: Write minimal code to pass
|
||||
3. **Refactor**: Improve code while keeping tests green
|
||||
|
||||
### TDD Rules
|
||||
- Never write production code without a failing test
|
||||
- Write only enough test to fail
|
||||
- Write only enough code to pass
|
||||
- Refactor continuously
|
||||
|
||||
## Coverage Requirements
|
||||
|
||||
### Minimum Standards
|
||||
- **Overall Coverage**: 85% minimum
|
||||
- **Critical Paths**: 95% minimum (auth, payments, data mutations)
|
||||
- **New Code**: 90% minimum
|
||||
|
||||
### What to Cover
|
||||
- All public interfaces
|
||||
- Error handling paths
|
||||
- Edge cases and boundaries
|
||||
- Integration points
|
||||
|
||||
### What NOT to Count
|
||||
- Generated code
|
||||
- Configuration files
|
||||
- Third-party library wrappers (thin wrappers only)
|
||||
|
||||
## Test Categories
|
||||
|
||||
### Unit Tests
|
||||
- Test single functions/methods in isolation
|
||||
- Mock external dependencies
|
||||
- Fast execution (< 100ms per test)
|
||||
- No network, database, or filesystem access
|
||||
|
||||
```python
|
||||
def test_calculate_discount_applies_percentage():
|
||||
result = calculate_discount(100, 0.20)
|
||||
assert result == 80
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
- Test multiple components together
|
||||
- Use real databases (test containers)
|
||||
- Test API contracts
|
||||
- Slower execution acceptable
|
||||
|
||||
```python
|
||||
def test_create_user_persists_to_database(db_session):
|
||||
user = create_user(db_session, "test@example.com")
|
||||
retrieved = get_user_by_email(db_session, "test@example.com")
|
||||
assert retrieved.id == user.id
|
||||
```
|
||||
|
||||
### End-to-End Tests
|
||||
- Test complete user workflows
|
||||
- Use real browser (Playwright, Cypress)
|
||||
- Test critical paths only (expensive to maintain)
|
||||
|
||||
```javascript
|
||||
test('user can complete checkout', async ({ page }) => {
|
||||
await page.goto('/products');
|
||||
await page.click('[data-testid="add-to-cart"]');
|
||||
await page.click('[data-testid="checkout"]');
|
||||
await page.fill('#email', 'test@example.com');
|
||||
await page.click('[data-testid="submit-order"]');
|
||||
await expect(page.locator('.order-confirmation')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Naming Convention
|
||||
```
|
||||
test_{what}_{condition}_{expected_result}
|
||||
|
||||
Examples:
|
||||
- test_login_with_valid_credentials_returns_token
|
||||
- test_login_with_invalid_password_returns_401
|
||||
- test_get_user_when_not_found_returns_404
|
||||
```
|
||||
|
||||
### Arrange-Act-Assert Pattern
|
||||
```python
|
||||
def test_add_item_to_cart_increases_count():
|
||||
# Arrange
|
||||
cart = Cart()
|
||||
item = Item(id=1, name="Widget", price=9.99)
|
||||
|
||||
# Act
|
||||
cart.add(item)
|
||||
|
||||
# Assert
|
||||
assert cart.item_count == 1
|
||||
assert cart.total == 9.99
|
||||
```
|
||||
|
||||
### Test Isolation
|
||||
- Each test should be independent
|
||||
- Use setup/teardown for common state
|
||||
- Clean up after tests
|
||||
- Don't rely on test execution order
|
||||
|
||||
## Mocking Guidelines
|
||||
|
||||
### When to Mock
|
||||
- External APIs and services
|
||||
- Time-dependent operations
|
||||
- Random number generation
|
||||
- Expensive operations
|
||||
|
||||
### When NOT to Mock
|
||||
- The code under test
|
||||
- Simple data structures
|
||||
- Database in integration tests
|
||||
|
||||
### Mock Example
|
||||
```python
|
||||
def test_send_notification_calls_email_service(mocker):
|
||||
mock_email = mocker.patch('services.email.send')
|
||||
|
||||
send_notification(user_id=1, message="Hello")
|
||||
|
||||
mock_email.assert_called_once_with(
|
||||
to="user@example.com",
|
||||
subject="Notification",
|
||||
body="Hello"
|
||||
)
|
||||
```
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Fixtures
|
||||
- Use factories for complex objects
|
||||
- Keep test data close to tests
|
||||
- Use realistic but anonymized data
|
||||
|
||||
### Database Tests
|
||||
- Use transactions with rollback
|
||||
- Or use test containers
|
||||
- Never test against production data
|
||||
|
||||
## Reporting
|
||||
|
||||
### Test Reports Should Include
|
||||
- Total tests run
|
||||
- Pass/fail counts
|
||||
- Coverage percentage
|
||||
- Execution time
|
||||
- Flaky test identification
|
||||
|
||||
### QA Report Template
|
||||
```markdown
|
||||
# QA Report - Issue #{number}
|
||||
|
||||
## Summary
|
||||
- Tests Added: X
|
||||
- Tests Modified: Y
|
||||
- Coverage: XX%
|
||||
|
||||
## Test Results
|
||||
- Passed: X
|
||||
- Failed: X
|
||||
- Skipped: X
|
||||
|
||||
## Coverage Analysis
|
||||
- Lines: XX%
|
||||
- Branches: XX%
|
||||
- Functions: XX%
|
||||
|
||||
## Notes
|
||||
[Any observations or concerns]
|
||||
```
|
||||
|
||||
## Commit Format
|
||||
```
|
||||
test(#34): Add user registration tests
|
||||
|
||||
- Unit tests for validation logic
|
||||
- Integration tests for /api/users endpoint
|
||||
- Coverage increased from 72% to 87%
|
||||
|
||||
Refs #34
|
||||
```
|
||||
|
||||
## Before Completing
|
||||
1. All tests pass locally
|
||||
2. Coverage meets 85% threshold
|
||||
3. No flaky tests introduced
|
||||
4. CI pipeline passes
|
||||
5. Update scratchpad with results
|
||||
382
guides/typescript.md
Normal file
382
guides/typescript.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# TypeScript Style Guide
|
||||
|
||||
**Authority**: This guide is MANDATORY for all TypeScript code. No exceptions without explicit approval.
|
||||
|
||||
Based on Google TypeScript Style Guide with stricter enforcement.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Explicit over implicit** — Always declare types, never rely on inference for public APIs
|
||||
2. **Specific over generic** — Use the narrowest type that works
|
||||
3. **Safe over convenient** — Type safety is not negotiable
|
||||
|
||||
---
|
||||
|
||||
## Forbidden Patterns (NEVER USE)
|
||||
|
||||
### `any` Type — FORBIDDEN
|
||||
```typescript
|
||||
// ❌ NEVER
|
||||
function process(data: any) { }
|
||||
const result: any = fetchData();
|
||||
Record<string, any>
|
||||
|
||||
// ✅ ALWAYS define explicit types
|
||||
interface UserData {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
function process(data: UserData) { }
|
||||
```
|
||||
|
||||
### `unknown` as Lazy Typing — FORBIDDEN
|
||||
`unknown` is only acceptable in these specific cases:
|
||||
1. Error catch blocks (then immediately narrow)
|
||||
2. JSON.parse results (then validate with Zod/schema)
|
||||
3. External API responses before validation
|
||||
|
||||
```typescript
|
||||
// ❌ NEVER - using unknown to avoid typing
|
||||
function getData(): unknown { }
|
||||
const config: Record<string, unknown> = {};
|
||||
|
||||
// ✅ ACCEPTABLE - error handling with immediate narrowing
|
||||
try {
|
||||
riskyOperation();
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
} else {
|
||||
logger.error('Unknown error', { error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ ACCEPTABLE - external data with validation
|
||||
const raw: unknown = JSON.parse(response);
|
||||
const validated = UserSchema.parse(raw); // Zod validation
|
||||
```
|
||||
|
||||
### Implicit `any` — FORBIDDEN
|
||||
```typescript
|
||||
// ❌ NEVER - implicit any from missing types
|
||||
function process(data) { } // Parameter has implicit any
|
||||
const handler = (e) => { } // Parameter has implicit any
|
||||
|
||||
// ✅ ALWAYS - explicit types
|
||||
function process(data: RequestPayload): ProcessedResult { }
|
||||
const handler = (e: React.MouseEvent<HTMLButtonElement>): void => { }
|
||||
```
|
||||
|
||||
### Type Assertions to Bypass Safety — FORBIDDEN
|
||||
```typescript
|
||||
// ❌ NEVER - lying to the compiler
|
||||
const user = data as User;
|
||||
const element = document.getElementById('app') as HTMLDivElement;
|
||||
|
||||
// ✅ USE - type guards and narrowing
|
||||
function isUser(data: unknown): data is User {
|
||||
return typeof data === 'object' && data !== null && 'id' in data;
|
||||
}
|
||||
if (isUser(data)) {
|
||||
console.log(data.id); // Safe
|
||||
}
|
||||
|
||||
// ✅ USE - null checks
|
||||
const element = document.getElementById('app');
|
||||
if (element instanceof HTMLDivElement) {
|
||||
element.style.display = 'none'; // Safe
|
||||
}
|
||||
```
|
||||
|
||||
### Non-null Assertion (`!`) — FORBIDDEN (except tests)
|
||||
```typescript
|
||||
// ❌ NEVER in production code
|
||||
const name = user!.name;
|
||||
const element = document.getElementById('app')!;
|
||||
|
||||
// ✅ USE - proper null handling
|
||||
const name = user?.name ?? 'Anonymous';
|
||||
const element = document.getElementById('app');
|
||||
if (element) {
|
||||
// Safe to use element
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Required Patterns
|
||||
|
||||
### Explicit Return Types — REQUIRED for all public functions
|
||||
```typescript
|
||||
// ❌ WRONG - missing return type
|
||||
export function calculateTotal(items: Item[]) {
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
|
||||
// ✅ CORRECT - explicit return type
|
||||
export function calculateTotal(items: Item[]): number {
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
```
|
||||
|
||||
### Explicit Parameter Types — REQUIRED always
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
const multiply = (a, b) => a * b;
|
||||
users.map(user => user.name); // If user type isn't inferred
|
||||
|
||||
// ✅ CORRECT
|
||||
const multiply = (a: number, b: number): number => a * b;
|
||||
users.map((user: User): string => user.name);
|
||||
```
|
||||
|
||||
### Interface Over Type Alias — PREFERRED for objects
|
||||
```typescript
|
||||
// ✅ PREFERRED - interface (extendable, better error messages)
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
// ✅ ACCEPTABLE - type alias for unions, intersections, primitives
|
||||
type Status = 'active' | 'inactive' | 'pending';
|
||||
type ID = string | number;
|
||||
```
|
||||
|
||||
### Const Assertions for Literals — REQUIRED
|
||||
```typescript
|
||||
// ❌ WRONG - loses literal types
|
||||
const config = {
|
||||
endpoint: '/api/users',
|
||||
method: 'GET',
|
||||
};
|
||||
// config.method is string, not 'GET'
|
||||
|
||||
// ✅ CORRECT - preserves literal types
|
||||
const config = {
|
||||
endpoint: '/api/users',
|
||||
method: 'GET',
|
||||
} as const;
|
||||
// config.method is 'GET'
|
||||
```
|
||||
|
||||
### Discriminated Unions — REQUIRED for variants
|
||||
```typescript
|
||||
// ❌ WRONG - optional properties for variants
|
||||
interface ApiResponse {
|
||||
success: boolean;
|
||||
data?: User;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ✅ CORRECT - discriminated union
|
||||
interface SuccessResponse {
|
||||
success: true;
|
||||
data: User;
|
||||
}
|
||||
interface ErrorResponse {
|
||||
success: false;
|
||||
error: string;
|
||||
}
|
||||
type ApiResponse = SuccessResponse | ErrorResponse;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generic Constraints
|
||||
|
||||
### Meaningful Constraints — REQUIRED
|
||||
```typescript
|
||||
// ❌ WRONG - unconstrained generic
|
||||
function merge<T>(a: T, b: T): T { }
|
||||
|
||||
// ✅ CORRECT - constrained generic
|
||||
function merge<T extends object>(a: T, b: Partial<T>): T { }
|
||||
```
|
||||
|
||||
### Default Generic Parameters — USE SPECIFIC TYPES
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
interface Repository<T = unknown> { }
|
||||
|
||||
// ✅ CORRECT - no default if type should be explicit
|
||||
interface Repository<T extends Entity> { }
|
||||
|
||||
// ✅ ACCEPTABLE - meaningful default
|
||||
interface Cache<T extends Serializable = JsonValue> { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## React/JSX Specific
|
||||
|
||||
### Event Handlers — EXPLICIT TYPES REQUIRED
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
const handleClick = (e) => { };
|
||||
const handleChange = (e) => { };
|
||||
|
||||
// ✅ CORRECT
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => { };
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => { };
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => { };
|
||||
```
|
||||
|
||||
### Component Props — INTERFACE REQUIRED
|
||||
```typescript
|
||||
// ❌ WRONG - inline types
|
||||
function Button({ label, onClick }: { label: string; onClick: () => void }) { }
|
||||
|
||||
// ✅ CORRECT - named interface
|
||||
interface ButtonProps {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function Button({ label, onClick, disabled = false }: ButtonProps): JSX.Element {
|
||||
return <button onClick={onClick} disabled={disabled}>{label}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
### Children Prop — USE React.ReactNode
|
||||
```typescript
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
sidebar?: React.ReactNode;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Response Typing
|
||||
|
||||
### Define Explicit Response Types
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
const response = await fetch('/api/users');
|
||||
const data = await response.json(); // data is any
|
||||
|
||||
// ✅ CORRECT
|
||||
interface UsersResponse {
|
||||
users: User[];
|
||||
pagination: PaginationInfo;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/users');
|
||||
const data: UsersResponse = await response.json();
|
||||
|
||||
// ✅ BEST - with runtime validation
|
||||
const response = await fetch('/api/users');
|
||||
const raw = await response.json();
|
||||
const data = UsersResponseSchema.parse(raw); // Zod validates at runtime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Typed Error Classes — REQUIRED for domain errors
|
||||
```typescript
|
||||
class ValidationError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly field: string,
|
||||
public readonly code: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
class NotFoundError extends Error {
|
||||
constructor(
|
||||
public readonly resource: string,
|
||||
public readonly id: string
|
||||
) {
|
||||
super(`${resource} with id ${id} not found`);
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Narrowing — REQUIRED
|
||||
```typescript
|
||||
try {
|
||||
await saveUser(user);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof ValidationError) {
|
||||
return { error: error.message, field: error.field };
|
||||
}
|
||||
if (error instanceof NotFoundError) {
|
||||
return { error: 'Not found', resource: error.resource };
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
logger.error('Unexpected error', { message: error.message, stack: error.stack });
|
||||
return { error: 'Internal error' };
|
||||
}
|
||||
logger.error('Unknown error type', { error: String(error) });
|
||||
return { error: 'Internal error' };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ESLint Rules — ENFORCE THESE
|
||||
|
||||
```javascript
|
||||
{
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error", {
|
||||
"allowExpressions": true,
|
||||
"allowTypedFunctionExpressions": true
|
||||
}],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "off", // Allow explicit primitives
|
||||
"@typescript-eslint/no-non-null-assertion": "error",
|
||||
"@typescript-eslint/strict-boolean-expressions": "error",
|
||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||
"@typescript-eslint/no-unsafe-call": "error",
|
||||
"@typescript-eslint/no-unsafe-return": "error"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TSConfig Strict Mode — REQUIRED
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary: The Type Safety Hierarchy
|
||||
|
||||
From best to worst:
|
||||
1. **Explicit specific type** (interface/type) — REQUIRED
|
||||
2. **Generic with constraints** — ACCEPTABLE
|
||||
3. **`unknown` with immediate validation** — ONLY for external data
|
||||
4. **`any`** — FORBIDDEN
|
||||
|
||||
**When in doubt, define an interface.**
|
||||
192
guides/vault-secrets.md
Normal file
192
guides/vault-secrets.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Vault Secrets Management Guide
|
||||
|
||||
This guide applies when the project uses HashiCorp Vault for secrets management.
|
||||
|
||||
## Before Starting
|
||||
1. Verify Vault access: `vault status`
|
||||
2. Authenticate: `vault login` (method depends on environment)
|
||||
3. Check your permissions for the required paths
|
||||
|
||||
## Canonical Structure
|
||||
|
||||
**ALL Vault secrets MUST follow this structure:**
|
||||
|
||||
```
|
||||
{mount}/{service}/{component}/{secret-name}
|
||||
```
|
||||
|
||||
### Components
|
||||
- **mount**: Environment-specific mount point
|
||||
- **service**: The service or application name
|
||||
- **component**: Logical grouping (database, api, oauth, etc.)
|
||||
- **secret-name**: Specific secret identifier
|
||||
|
||||
## Environment Mounts
|
||||
|
||||
| Mount | Environment | Usage |
|
||||
|-------|-------------|-------|
|
||||
| `secret-dev/` | Development | Local dev, CI |
|
||||
| `secret-staging/` | Staging | Pre-production testing |
|
||||
| `secret-prod/` | Production | Live systems |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Database credentials
|
||||
secret-prod/postgres/database/app
|
||||
secret-prod/mysql/database/readonly
|
||||
secret-staging/redis/auth/default
|
||||
|
||||
# API tokens
|
||||
secret-prod/authentik/admin/token
|
||||
secret-prod/stripe/api/live-key
|
||||
secret-dev/sendgrid/api/test-key
|
||||
|
||||
# JWT/Authentication
|
||||
secret-prod/backend-api/jwt/signing-key
|
||||
secret-prod/auth-service/session/secret
|
||||
|
||||
# OAuth providers
|
||||
secret-prod/backend-api/oauth/google
|
||||
secret-prod/backend-api/oauth/github
|
||||
|
||||
# Internal services
|
||||
secret-prod/loki/read-auth/admin
|
||||
secret-prod/grafana/admin/password
|
||||
```
|
||||
|
||||
## Standard Field Names
|
||||
|
||||
Use consistent field names within secrets:
|
||||
|
||||
| Purpose | Fields |
|
||||
|---------|--------|
|
||||
| Credentials | `username`, `password` |
|
||||
| Tokens | `token` |
|
||||
| OAuth | `client_id`, `client_secret` |
|
||||
| Connection | `url`, `host`, `port` |
|
||||
| Keys | `public_key`, `private_key` |
|
||||
|
||||
### Example Secret Structure
|
||||
```json
|
||||
// secret-prod/postgres/database/app
|
||||
{
|
||||
"username": "app_user",
|
||||
"password": "secure-password-here",
|
||||
"host": "db.example.com",
|
||||
"port": "5432",
|
||||
"database": "myapp"
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. **DO NOT GUESS** secret paths - Always verify the path exists
|
||||
2. **Use helper scripts** in `scripts/vault/` when available
|
||||
3. **All lowercase, hyphenated** (kebab-case) for all path segments
|
||||
4. **Standard field names** - Use the conventions above
|
||||
5. **No sensitive data in path names** - Path itself should not reveal secrets
|
||||
6. **Environment separation** - Never reference prod secrets from dev
|
||||
|
||||
## Deprecated Paths (DO NOT USE)
|
||||
|
||||
These legacy patterns are deprecated and should be migrated:
|
||||
|
||||
| Deprecated | Migrate To |
|
||||
|------------|------------|
|
||||
| `secret/infrastructure/*` | `secret-{env}/{service}/...` |
|
||||
| `secret/oauth/*` | `secret-{env}/{service}/oauth/{provider}` |
|
||||
| `secret/database/*` | `secret-{env}/{service}/database/{user}` |
|
||||
| `secret/credentials/*` | `secret-{env}/{service}/{component}/{name}` |
|
||||
|
||||
## Reading Secrets
|
||||
|
||||
### CLI
|
||||
```bash
|
||||
# Read a secret
|
||||
vault kv get secret-prod/postgres/database/app
|
||||
|
||||
# Get specific field
|
||||
vault kv get -field=password secret-prod/postgres/database/app
|
||||
|
||||
# JSON output
|
||||
vault kv get -format=json secret-prod/postgres/database/app
|
||||
```
|
||||
|
||||
### Application Code
|
||||
|
||||
**Python (hvac):**
|
||||
```python
|
||||
import hvac
|
||||
|
||||
client = hvac.Client(url='https://vault.example.com')
|
||||
secret = client.secrets.kv.v2.read_secret_version(
|
||||
path='postgres/database/app',
|
||||
mount_point='secret-prod'
|
||||
)
|
||||
password = secret['data']['data']['password']
|
||||
```
|
||||
|
||||
**Node.js (node-vault):**
|
||||
```javascript
|
||||
const vault = require('node-vault')({ endpoint: 'https://vault.example.com' });
|
||||
const secret = await vault.read('secret-prod/data/postgres/database/app');
|
||||
const password = secret.data.data.password;
|
||||
```
|
||||
|
||||
**Go:**
|
||||
```go
|
||||
secret, err := client.Logical().Read("secret-prod/data/postgres/database/app")
|
||||
password := secret.Data["data"].(map[string]interface{})["password"].(string)
|
||||
```
|
||||
|
||||
## Writing Secrets
|
||||
|
||||
Only authorized personnel should write secrets. If you need a new secret:
|
||||
|
||||
1. Request through proper channels (ticket, PR to IaC repo)
|
||||
2. Follow the canonical structure
|
||||
3. Document the secret's purpose
|
||||
4. Set appropriate access policies
|
||||
|
||||
```bash
|
||||
# Example (requires write permissions)
|
||||
vault kv put secret-dev/myapp/database/app \
|
||||
username="dev_user" \
|
||||
password="dev-password" \
|
||||
host="localhost" \
|
||||
port="5432"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied
|
||||
```
|
||||
Error: permission denied
|
||||
```
|
||||
- Verify your token has read access to the path
|
||||
- Check if you're using the correct mount point
|
||||
- Confirm the secret path exists
|
||||
|
||||
### Secret Not Found
|
||||
```
|
||||
Error: no value found at secret-prod/data/service/component/name
|
||||
```
|
||||
- Verify the exact path (use `vault kv list` to explore)
|
||||
- Check for typos in service/component names
|
||||
- Confirm you're using the correct environment mount
|
||||
|
||||
### Token Expired
|
||||
```
|
||||
Error: token expired
|
||||
```
|
||||
- Re-authenticate: `vault login`
|
||||
- Check token TTL: `vault token lookup`
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Least privilege** - Request only the permissions you need
|
||||
2. **Short-lived tokens** - Use tokens with appropriate TTLs
|
||||
3. **Audit logging** - All access is logged; act accordingly
|
||||
4. **No local copies** - Don't store secrets in files or env vars long-term
|
||||
5. **Rotate on compromise** - Immediately rotate any exposed secrets
|
||||
Reference in New Issue
Block a user