npm package: @soulbatical/tetra-dev-toolkit
Description: Developer toolkit for Tetra projects - audit, dev-token, quality checks
Author: Albert Barth
Latest version: 1.22.2

# @soulbatical/tetra-dev-toolkit

Quality, security, and hygiene checks for all Soulbatical projects.

## Install

```bash
npm install --save-dev @soulbatical/tetra-dev-toolkit
```

## Setup (A-Z)

One command installs everything — hooks, CI, config:

```bash
npx tetra-setup
```

This creates:
- `.husky/pre-commit` — quick security checks + migration lint before every commit
- `.husky/pre-push` — full security audit + RLS security gate before every push
- `.github/workflows/quality.yml` — full audit on PR/push to main
- `.tetra-quality.json` — project config (override defaults)

To install individual components:

```bash
npx tetra-setup hooks    # Husky hooks only
npx tetra-setup ci       # GitHub Actions only
npx tetra-setup config   # Config file only
```

---

## 8-Layer Security Architecture

Every Tetra project is protected by 8 layers. From the moment a developer writes code to the moment a database query executes — every layer enforces security independently. If one layer is bypassed, the next catches it.

See [SECURITY.md](./SECURITY.md) for the complete architecture reference.

```
LAYER 1: CONFIG FILES         Feature configs define what SHOULD happen
LAYER 2: PRE-COMMIT           Quick checks + migration lint on staged files
LAYER 3: PRE-PUSH             Full security audit (12 checks) + live RLS gate
LAYER 4: MIGRATION PUSH       tetra-db-push blocks unsafe SQL before Supabase
LAYER 5: CI/CD                TypeScript compile + tests + build
LAYER 6: RUNTIME              Express middleware (HTTPS, CORS, auth, rate limit, input sanitize)
LAYER 7: DATABASE              RLS policies enforce org/user isolation on every query
LAYER 8: DB HELPERS            adminDB/userDB/publicDB/systemDB enforce correct access pattern
```

---

## CLI Tools

| Command | Description |
|---------|-------------|
| `tetra-audit` | Run quality/security/hygiene checks |
| `tetra-audit quick` | Quick critical checks (pre-commit) |
| `tetra-audit security` | Full security suite (12 checks) |
| `tetra-audit stability` | Stability suite (16 checks) |
| `tetra-audit codeQuality` | Code quality suite (4 checks) |
| `tetra-audit supabase` | Supabase suite (3 checks) |
| `tetra-audit hygiene` | Repo hygiene suite (2 checks) |
| `tetra-audit --ci` | CI mode (GitHub Actions annotations) |
| `tetra-audit --json` | JSON output |
| `tetra-audit --verbose` | Detailed output with fix suggestions |
| `tetra-migration-lint` | Offline SQL migration linter (8 rules) |
| `tetra-migration-lint --staged` | Only git-staged .sql files (pre-commit hook) |
| `tetra-migration-lint --fix-suggestions` | Show fix SQL per violation |
| `tetra-db-push` | Safe wrapper: lint + `supabase db push` |
| `tetra-check-rls` | RLS security gate against live Supabase |
| `tetra-check-rls --fix` | Generate hardening migration SQL |
| `tetra-setup` | Install hooks, CI, and config |
| `tetra-init` | Initialize project config files |
| `tetra-dev-token` | Generate development tokens |
| `tetra-license generate` | Generate a Tetra license key |
| `tetra-license verify` | Verify a license key (or `TETRA_LICENSE_KEY` env) |
| `tetra-license decode` | Decode key payload without validation |

Exit codes: `0` = passed, `1` = failed (CRITICAL/HIGH), `2` = error. No middle ground.

---

## Check Suites

### Security (12 checks)

| Check | Severity | What it catches |
|-------|----------|-----------------|
| `hardcoded-secrets` | critical | API keys, tokens, JWTs in source code |
| `service-key-exposure` | critical | Supabase service role keys in frontend |
| `deprecated-supabase-admin` | critical | Legacy `supabaseAdmin` patterns |
| `direct-supabase-client` | critical | Direct `createClient` imports outside core wrappers |
| `frontend-supabase-queries` | critical | `.from()` / `.rpc()` / `.storage` calls in frontend code |
| `tetra-core-compliance` | critical | Missing configureAuth, authenticateToken, or db helpers |
| `mixed-db-usage` | critical | Controller uses wrong DB helper or mixes types |
| `config-rls-alignment` | critical | Feature config accessLevel does not match RLS policies |
| `rpc-security-mode` | critical | SECURITY DEFINER on data RPCs (bypasses RLS) |
| `route-config-alignment` | high | Route middleware does not match config accessLevel |
| `systemdb-whitelist` | high | systemDB() in unauthorized contexts |
| `gitignore-validation` | high | Missing .gitignore entries, tracked .env files |

### Migration Lint (8 rules)

| Rule | Severity | What it catches |
|------|----------|-----------------|
| `DEFINER_DATA_RPC` | critical | SECURITY DEFINER on data RPCs |
| `CREATE_TABLE_NO_RLS` | critical | New table without ENABLE ROW LEVEL SECURITY |
| `DISABLE_RLS` | critical | ALTER TABLE ... DISABLE RLS |
| `USING_TRUE_WRITE` | critical | USING(true) on INSERT/UPDATE/DELETE/ALL policies |
| `GRANT_PUBLIC_ANON` | critical | GRANT write permissions to public/anon |
| `RAW_SERVICE_KEY` | critical | Hardcoded JWT in migration file |
| `DROP_POLICY` | high | DROP POLICY without replacement in same migration |
| `POLICY_NO_WITH_CHECK` | medium | INSERT/UPDATE policy without WITH CHECK clause |

### Supabase (3 checks, auto-detected)

| Check | Severity | What it catches |
|-------|----------|-----------------|
| `rls-policy-audit` | critical | Tables without Row Level Security |
| `rpc-param-mismatch` | critical | TypeScript `.rpc()` calls with wrong parameter names vs SQL |
| `rpc-generator-origin` | high | RPC functions not generated by Tetra SQL Generator |

### Stability (16 checks)

| Check | Severity | What it catches |
|-------|----------|-----------------|
| `husky-hooks` | medium | Missing pre-commit/pre-push hooks |
| `ci-pipeline` | medium | Missing or incomplete CI config |
| `npm-audit` | high | Known vulnerabilities in dependencies |
| `tests` | high | Missing test framework, test files, or test scripts |
| `eslint-security` | high | Missing ESLint config, security plugin, or SonarJS plugin |
| `typescript-strict` | medium | TypeScript strict mode not enabled |
| `coverage-thresholds` | medium | No coverage thresholds configured |
| `knip` | medium | Dead code: unused files, dependencies, exports |
| `dependency-cruiser` | medium | Circular dependencies, architecture violations |
| `dependency-automation` | medium | No Dependabot or Renovate configured |
| `prettier` | low | No code formatter configured |
| `conventional-commits` | low | No commit message convention enforced |
| `bundle-size` | low | No bundle size monitoring |
| `sast` | medium | No static application security testing |
| `license-audit` | low | No license compliance checking |
| `security-layers` | high | Missing security layers (auth, rate limiting, CORS, etc.) |

### Code Quality (4 checks)

| Check | Severity | What it catches |
|-------|----------|-----------------|
| `api-response-format` | medium | Non-standard response format |
| `file-size` | medium | Files exceeding line limits |
| `naming-conventions` | medium | Inconsistent file/dir naming |
| `route-separation` | high | Business logic in route files |

### Hygiene (2 checks)

| Check | Severity | What it catches |
|-------|----------|-----------------|
| `file-organization` | high | Stray .md, .sh, clutter in code dirs |
| `stella-compliance` | medium | Missing Stella integration |

---

## Supabase Client Architecture

Every Tetra project MUST use the 5 db helpers. Direct `createClient` imports are **blocked** by the `direct-supabase-client` check.

### The 5 DB Helpers

| Helper | Key Used | RLS | When to Use |
|--------|----------|-----|-------------|
| `adminDB(req)` | Anon + user JWT | Enforced | Admin panel queries (org-scoped) |
| `userDB(req)` | Anon + user JWT | Enforced | User's own data |
| `publicDB()` | Anon key | Enforced | Public/anonymous access |
| `superadminDB(req)` | Service Role | Bypassed | Cross-org operations (superadmin only, audit logged) |
| `systemDB(context)` | Service Role | Bypassed | Webhooks, crons, system ops (whitelisted, audit logged) |

### Why this matters

```
createClient(url, SERVICE_ROLE_KEY)  →  Bypasses ALL RLS. No audit trail. No access control.
adminDB(req)                         →  Uses user's JWT. RLS enforces org boundaries. Audit logged.
```

A single `createClient` call with the service role key can leak data across all organizations. The db helpers enforce the security boundary at the application layer.

---

## Configuration

Override defaults in `.tetra-quality.json`:

```json
{
  "suites": {
    "security": true,
    "stability": true,
    "codeQuality": true,
    "supabase": "auto",
    "hygiene": true
  },
  "supabase": {
    "publicRpcFunctions": ["get_public_data"],
    "publicTables": ["public_lookup"],
    "backendOnlyTables": ["system_logs", "cron_state"],
    "securityDefinerWhitelist": ["auth_org_id", "auth_uid"]
  },
  "ignore": ["**/legacy/**", "**/scripts/**"]
}
```

---

## New Project Checklist

```bash
# 1. Install hooks (creates pre-commit + pre-push)
npx tetra-setup hooks

# 2. Run full audit
npx tetra-audit

# 3. Run RLS gate manually
doppler run -- npx tetra-check-rls

# 4. Verify migration lint works
npx tetra-migration-lint

# 5. Add to Railway build command
# doppler run -- npx tetra-check-rls --errors-only && npm run build
```

If any step fails, fix it before writing code. No exceptions.

---

## Claude Code statusline

A 3-line statusline for Claude Code that shows context usage, costs, project health, and session metadata.

### Setup

```bash
# Symlink the script
ln -sf /path/to/tetra/packages/dev-toolkit/hooks/statusline.sh ~/.claude/hooks/statusline.sh

# Add to ~/.claude/settings.json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/hooks/statusline.sh"
  }
}
```

### What it shows

```
repo: vibecodingacademy  tasks: 3 open / 12 done  started by: user  cmux: workspace:308 ⠂ Claude Code
opus  ██████░░░░░░░░░ 44%  context: 89K / 200K  99% cached  this turn: +7K
$3.76  5m20s  (api: 3m5s)  lines: +47 -12  turn #5  main*  v2.1.73
```

**Line 1 — project dashboard**

| Field | Source | Description |
|-------|--------|-------------|
| `repo:` | `workspace.project_dir` | Current project directory name |
| `tasks:` | `.ralph/@fix_plan.md` | Open/done task count from Ralph fix plan |
| `started by:` | Process tree + cmux | `user`, `monica`, `ralph`, `cursor`, `vscode` |
| `cmux:` | `cmux identify` + `cmux list-workspaces` | Workspace ref and name (only in cmux terminal) |

**Line 2 — context window**

| Field | Source | Description |
|-------|--------|-------------|
| Model | `model.id` | Short name: `opus`, `sonnet`, `haiku` |
| Progress bar | `context_window.used_percentage` | 15-char bar, green <50%, yellow 50-80%, red >80% |
| `context:` | input + cache tokens | Effective tokens in window vs max |
| `cached` | `cache_read / context` | % of context served from prompt cache (saves cost) |
| `this turn:` | Delta from previous turn | Token growth this turn (helps spot expensive hooks) |

**Line 3 — session stats**

| Field | Source | Description |
|-------|--------|-------------|
| Cost | `cost.total_cost_usd` | Session cost so far |
| Duration | `cost.total_duration_ms` | Wall clock time |
| API time | `cost.total_api_duration_ms` | Time spent waiting for API responses |
| Lines | `cost.total_lines_added/removed` | Code changes this session |
| Turn | Delta tracker | Number of assistant responses |
| Branch | `git branch` | Current branch, `*` if dirty |
| Version | `version` | Claude Code version |

### How "started by" detection works

1. If running in cmux: checks workspace name — `monica:*` → `monica`
2. Fallback: walks the process tree looking for `ralph`, `cursor`, `code` (VS Code)
3. Default: `user` (manual terminal session)

### Task colors

| Color | Meaning |
|-------|---------|
| Green | 0 open tasks (all done) |
| Yellow | 1-10 open tasks |
| Red | 10+ open tasks |

Projects without `.ralph/@fix_plan.md` don't show the tasks field.

---

## Config-Driven E2E Testing (v5.0)

Auto-generate CRUD E2E tests from FeatureConfig. Zero boilerplate — add a `testing` section to your feature config and tests are generated automatically.

### How it works

1. Each `FeatureConfig` gets a `testing` section defining create/update bodies and required fields
2. A single test file imports all configs and generates list/create/read/update/delete + validation tests
3. Read-only resources just set `skip: { create: true, update: true, delete: true }`

### Step 1: Add `testing` to your feature config

```typescript
// backend/src/features/adcampaigns/config/adcampaigns.config.ts
export const adcampaignsFeatureConfig: FeatureConfig = {
  tableName: 'ad_campaigns',
  // ... existing config ...

  testing: {
    restBasePath: '/api/admin/ad-campaigns',
    createBody: { name: 'E2E Test Campaign $timestamp', platform: 'facebook' },
    updateBody: { name: 'E2E Updated Campaign' },
    updateMethod: 'PATCH',          // or 'PUT' (default)
    createStatus: 200,              // expected status (default: 200, use 201 if your API returns that)
    requiredFields: ['name', 'platform'],  // generates validation tests
  }
};
```

For read-only resources:
```typescript
testing: {
  restBasePath: '/api/admin/style-library',
  createBody: {},
  updateBody: {},
  skip: { create: true, update: true, delete: true },
}
```

### Step 2: Create the test file

```typescript
// tests/e2e/02-crud-resources.test.ts
import { describe, it, expect, beforeAll } from 'vitest';
import { get, post, del, api } from './helpers/api-client';
import { getTestContext, type TestContext } from './helpers/test-users';

import { adcampaignsFeatureConfig } from '../../backend/src/features/adcampaigns/config/adcampaigns.config';
import { projectsFeatureConfig } from '../../backend/src/features/projects/config/projects.config';
// ... import all configs ...

const allConfigs = [adcampaignsFeatureConfig, projectsFeatureConfig /* ... */];

let ctx: TestContext;
beforeAll(async () => { ctx = await getTestContext(); }, 60000);

for (const config of allConfigs) {
  const testing = config.testing;
  if (!testing) continue;
  const basePath = testing.restBasePath;

  describe(`CRUD: ${config.tableName}`, () => {
    let createdId: string;

    it(`GET ${basePath} returns 200`, async () => { /* ... */ });
    if (!testing.skip?.create) {
      it(`POST ${basePath} creates`, async () => { /* ... */ });
      it(`GET ${basePath}/:id reads`, async () => { /* ... */ });
      it(`${testing.updateMethod} ${basePath}/:id updates`, async () => { /* ... */ });
      it(`DELETE ${basePath}/:id deletes`, async () => { /* ... */ });
    }
  });
}
```

See `agentrook/tests/e2e/02-crud-resources.test.ts` for a full working example.

### Step 3: Run

```bash
npx vitest run --config vitest.config.e2e.ts
```

### TestingConfig reference

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `restBasePath` | string | `config.restBasePath` | API endpoint path |
| `createBody` | object | required | POST body for create |
| `updateBody` | object | required | PUT/PATCH body for update |
| `updateMethod` | `'PUT' \| 'PATCH'` | `'PUT'` | HTTP method for updates |
| `createStatus` | number | `200` | Expected create response status |
| `requiredFields` | string[] | `[]` | Fields to test validation for |
| `skip.create` | boolean | `false` | Skip create/read/update/delete |
| `skip.read` | boolean | `false` | Skip read test |
| `skip.update` | boolean | `false` | Skip update test |
| `skip.delete` | boolean | `false` | Skip delete test |

### Dynamic values

Use `$timestamp` and `$random` in string values for unique test data:

```typescript
createBody: { name: 'Test $timestamp', key: 'e2e-$random' }
// Resolves to: { name: 'Test 1710782400000', key: 'e2e-x8k2m9' }
```

### Runtime utility

For advanced usage, import the generator directly:

```typescript
import { generateCrudTests } from '@soulbatical/tetra-dev-toolkit/testing';
```

### Layer 2: Permission + Business Flow Tests

Config-driven tests cover **breadth** (every endpoint). Layer 2 tests cover **depth** — these must be written manually per project.

#### Permission tests (`03-permissions.test.ts`)

Test role-based access control with two test accounts (admin + member):

```typescript
// Tests per admin endpoint:
// 1. Admin GET → 200 ✅
// 2. Member GET → 403 ✅ (catches missing middleware!)
// 3. No token → 401 ✅
// Plus: superadmin routes blocked for admin, write ops blocked for member
```

This catches real security bugs. Agentrook findings:
- `/api/admin/ad-creatives` — missing `requireOrganizationAdmin`
- `/api/admin/style-library` — missing `requireOrganizationAdmin`

#### Business flow tests (`04-business-flows.test.ts`)

Multi-step scenarios that span resources:

```typescript
// Flow: Project → Campaign (cross-resource linking)
// Flow: Brand Profile → Asset (parent-child)
// Flow: Status lifecycle (draft → running → paused → completed)
// Flow: Duplicate key handling
// Flow: Empty update → 400
// Flow: Nonexistent resource → 404
```

### Test user setup

**CRITICAL**: Never create Supabase Auth users via raw SQL — the password hash is incompatible with GoTrue. Use the GoTrue Admin API:

```bash
SERVICE_KEY=$(doppler secrets get SUPABASE_SERVICE_ROLE_KEY --plain --project X --config Y)

curl -X POST "https://PROJECT.supabase.co/auth/v1/admin/users" \
  -H "Authorization: Bearer $SERVICE_KEY" \
  -H "apikey: $SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"e2e-admin@project.com","password":"TestPass1234","email_confirm":true}'
```

Then add `users_public` + `organization_members` records via SQL.

### 3-layer test pyramid

```
Layer 3: Browser smoke (gstack)     — 5-10 user flows
Layer 2: Permission + business flow — catches security bugs, logic errors
Layer 1: Config-driven CRUD         — catches broken endpoints, missing fields
```

| Layer | Auto-generated? | What it catches |
|-------|----------------|-----------------|
| 1 | Yes (from config) | Broken routes, 500s, missing fields |
| 2 | No (manual) | Permission bugs, business logic, edge cases |
| 3 | No (gstack) | Frontend integration, UX issues |

### File structure

```
tests/e2e/
  helpers/api-client.ts           — HTTP client with X-Test-Key
  helpers/test-users.ts           — Login + token caching
  global-setup.ts                 — Login test accounts
  02-crud-resources.test.ts       — Layer 1: config-driven
  03-permissions.test.ts          — Layer 2: role access
  04-business-flows.test.ts       — Layer 2: multi-step
tests/e2e-auth/                   — Separate (invalidates tokens)
  01-auth.test.ts                 — Login, refresh, logout
  07-security.test.ts             — Auth walls
```

**Important**: Auth/security tests must run in a separate directory — they invalidate shared tokens.

---

## Changelog

### 1.16.0

**New: Full Stability Suite (16 checks)**
- Stability suite expanded from 3 → 16 checks via health check adapter
- New checks: tests, eslint-security, typescript-strict, coverage-thresholds, knip, dependency-cruiser, dependency-automation, prettier, conventional-commits, bundle-size, sast, license-audit, security-layers
- `tetra-audit stability` now catches missing test infrastructure, dead code, formatting, and security layer gaps
- Health checks (score-based) automatically adapted to runner format (pass/fail)
- RPC generator: SECURITY DEFINER → SECURITY INVOKER for all data RPCs
- Migration lint: expanded whitelist for legitimate DEFINER functions
- Mixed DB checker: fixed regex that matched `superadminDB` as `adminDB`
- Route config checker: support `@tetra-audit-ignore` directive

### 1.15.0

**New: Migration Lint + DB Push Guard**
- `tetra-migration-lint` — offline SQL migration linter (8 rules)
- `tetra-db-push` — safe wrapper around `supabase db push` (lint first, push second)
- Pre-commit hook now lints staged `.sql` files automatically
- Rules: DEFINER on data RPCs, CREATE TABLE without RLS, DISABLE RLS, USING(true) writes, GRANT public/anon, DROP POLICY without replacement, missing WITH CHECK, hardcoded JWT

### 1.14.0

**New: Config-RLS Alignment + Route-Config Alignment + RPC Security Mode**
- `config-rls-alignment` — verifies feature config accessLevel matches RLS policies on the table
- `route-config-alignment` — verifies route middleware matches config accessLevel (admin routes need auth middleware)
- `rpc-security-mode` — scans ALL RPCs for SECURITY DEFINER (must be INVOKER unless whitelisted)

### 1.13.0

**New: Mixed DB Usage Detection**
- `mixed-db-usage` — controllers must use exactly ONE DB helper type matching their naming convention
- AdminController → adminDB only, UserController → userDB only, etc.
- Detects systemDB misuse when authenticated user context is available

### 1.12.0

**New: Tetra Core Compliance**
- `tetra-core-compliance` — if a project has @soulbatical/tetra-core, it MUST use configureAuth, authenticateToken, and db helpers
- No more "skipped" checks — if tetra-core is a dependency, ALL security checks run

### 1.11.0

**New: Frontend Supabase Queries check**
- `frontend-supabase-queries` — blocks `.from()`, `.rpc()`, `.storage` in frontend code
- Frontend MUST use API client, never query Supabase directly

### 1.10.0

**Improved: RLS Policy Audit**
- Tables with RLS ON but 0 policies now flagged as CRITICAL (was HIGH)
- New config: `supabase.backendOnlyTables` for tables that intentionally have no policies

### 1.9.0

**New: Direct Supabase Client check + 3-Layer Security Model**

### 1.3.0

**New: Hygiene suite + RPC Param Mismatch check**

### 1.2.0

- Initial public version