Skip to content

Enforcement Methods

How the 10 commandments are enforced across the homelab.

Three Layers of Enforcement

Layer 1: Database Constraints (Strongest)
  └─ Invalid states impossible (CHECK constraints, FOREIGN KEYS)

Layer 2: API Validation (Strong)
  └─ Invalid requests rejected (Pydantic models, business logic)

Layer 3: Git Hooks & Documentation (Preventive)
  └─ Bad patterns blocked before commit (pre-commit hooks, CLAUDE.md)

Layer 1: Database Constraints (Impossible to Bypass)

Example (CharlieHub TCP Routing):

-- TCP routes MUST have entrypoint and backend
ALTER TABLE domains ADD CONSTRAINT chk_tcp_requires_backend
CHECK (
  protocol != 'tcp' OR (
    backend_host IS NOT NULL AND
    backend_port IS NOT NULL
  )
);

-- TCP routes CANNOT use HTTP-only features
ALTER TABLE domains ADD CONSTRAINT chk_tcp_no_http_features
CHECK (
  protocol != 'tcp' OR (
    cors_enabled = false AND
    default_path IS NULL
  )
);

Result: Invalid states are structurally impossible

# Even direct SQL will fail
docker exec postgres psql -c "
  INSERT INTO domains (domain='test.com', protocol='tcp', cors_enabled=true)
"
# ERROR: new row for relation "domains" violates check constraint

Layer 2: API Validation (Friendly Errors)

Example (CharlieHub Domain Manager API):

class DomainBase(BaseModel):
    protocol: Literal["http", "tcp"]
    tcp_entrypoint: Optional[str]
    cors_enabled: bool = False

    @field_validator('tcp_entrypoint')
    def validate_tcp_entrypoint(cls, v, info):
        if info.data.get('protocol') == 'tcp' and not v:
            raise ValueError('TCP routes require tcp_entrypoint')
        return v

    @field_validator('cors_enabled')
    def validate_cors(cls, v, info):
        if info.data.get('protocol') == 'tcp' and v:
            raise ValueError('TCP routes cannot use CORS')
        return v

Result: Clear, helpful error messages

curl -X POST /api/domains \
  -d '{"protocol": "tcp", "cors_enabled": true}'

# Response: 422 Unprocessable Entity
# "TCP routes cannot use CORS (HTTP-only feature)"

Layer 3: Documentation & Git Hooks

CLAUDE.md on each node:

Every node has a CLAUDE.md that either: 1. References this central documentation (px1, px2, px3, px5) 2. Extends with node-specific rules (charliehub)

Example (px1 CLAUDE.md):

# CLAUDE.md - px1 Homelab Standards

See centralized standards:
https://hub2:8000/standards/

All px1 configurations follow these rules.

Optional: Git Hooks (prevents bad commits)

#!/bin/bash
# .git/hooks/pre-commit

# Check for direct SQL edits to config tables
if git diff --cached | grep -i "INSERT INTO domains" | grep -v "-- migration"; then
    echo "❌ ERROR: Direct SQL inserts detected"
    echo "Use the API instead: curl -X POST /api/domains"
    exit 1
fi

# Check for manual YAML routes
if git diff --cached --name-only | grep "config/core.*route"; then
    echo "❌ ERROR: Manual route file detected"
    echo "Use the API to generate routes automatically"
    exit 1
fi

# Check for Docker labels (if provider is disabled)
if git diff --cached | grep "traefik.http.routers"; then
    echo "⚠️  WARNING: Docker labels detected"
    echo "The Docker provider is disabled on this system"
    exit 1
fi

exit 0

Per-Node Enforcement

charliehub

  • ✅ Database constraints (TCP routing)
  • ✅ API validation (Pydantic models)
  • ✅ Pre-commit hook (git-hooks.log)
  • ✅ Detailed CLAUDE.md

px1, px2, px3, px5

  • ✅ Documentation reference (CLAUDE.md → mkdocs)
  • ✅ Optional: Git hooks (if applicable)
  • ⏳ Future: Database-specific constraints (when APIs are built)

What Happens When You Try to Bypass

Scenario 1: Edit Generated Routes.yml

$ vim /traefik/config/generated/routes.yml
# Make some manual edits
$ git add .
$ git commit -m "manual route"
# On next generation, your edits are LOST
# routes.yml is regenerated from source (database)

Prevention: Document says "read-only artifact", pre-commit hook warns


Scenario 2: Insert Route Directly to Database

$ docker exec postgres psql \
  -c "INSERT INTO domains (protocol='tcp', cors_enabled=true)"

# ERROR: new row for relation "domains" violates check constraint "chk_tcp_no_http_features"

Prevention: Database constraint makes it impossible


Scenario 3: Add Docker Label for Routing

services:
  my-service:
    labels:
      traefik.http.routers.service.rule: "Host(`test.com`)"
# This does nothing - Docker provider is disabled
# Route doesn't appear in Traefik
# User wastes time wondering why it doesn't work

Prevention: Documentation says not to do this, pre-commit hook warns


Scenario 4: Create Manual Route YAML

$ sudo vim /traefik/config/core/emergency-route.yml
# Adds route manually, promises to remove it later
# 6 months later: nobody remembers it's there

Prevention: Pre-commit hook blocks, documentation says not to, CLAUDE.md reminds developer


Summary: Layered Defense

Bypass Method Layer 1 Layer 2 Layer 3 Result
Edit output file ✗ Regenerated ✗ Ignored ⚠️ Warned Change lost
Direct SQL (invalid) 🔒 Blocked - - Impossible
Docker labels ✗ Provider off ✗ Rejected ⚠️ Warned No effect
Manual YAML ✗ Generated - ⚠️ Blocked Can't commit
Direct SQL (valid) ✓ Allowed ✗ Audit miss ⚠️ Warned Audit gap

Conclusion: Multiple layers catch different bypass attempts. Constraints make invalid states impossible. API validation catches mistakes. Documentation prevents confusion.


How to Add New Constraints

If you discover a pattern that should be impossible:

  1. Identify the rule: "TCP routes should not have X"
  2. Add to database: ALTER TABLE ... ADD CONSTRAINT chk_...
  3. Update API validation: Add to Pydantic model
  4. Test: Try to violate it, verify it fails
  5. Document: Add to CLAUDE.md and standards
  6. Commit: Git commit with explanation

Example (CharlieHub):

ALTER TABLE domains ADD CONSTRAINT chk_tcp_requires_entrypoint
CHECK (protocol != 'tcp' OR tcp_entrypoint IS NOT NULL);

This makes the rule: - Impossible to bypass (Layer 1) - Clear to API users (Layer 2) - Documented for future developers (Layer 3)