
enforcing-principles
by krwhynot
SKILL.md
name: enforcing-principles description: Use when implementing features, handling errors, adding validation, creating forms, writing database migrations. Enforces fail-fast (NO retry logic, circuit breakers), single entry point (unified data provider, Zod at API boundary), form defaults from schema (zodSchema.partial().parse), TypeScript (interface vs type), React Admin patterns. NEW (2024-12): Zod security (z.strictObject, string .max() limits, z.coerce for forms, z.enum allowlist), form performance (onSubmit/onBlur mode, useWatch not watch). BLOCKS anti-patterns, SUGGESTS best practices.
Atomic CRM Engineering Constitution
Overview
Enforce Atomic CRM's Engineering Constitution principles to prevent over-engineering and maintain codebase velocity. Most critical: fail fast (no retry logic/circuit breakers) and single composable entry point (unified data provider delegating to resource modules, Zod validation at API boundary).
Core principle: Pre-launch phase prioritizes velocity over resilience. Simple solutions over clever ones.
When to Use
Use this skill when:
- Handling errors or adding resilience
- Adding validation or data transforms
- Creating forms with default values
- Defining TypeScript types
- Editing existing files
- Creating database migrations
Do NOT use for:
- Reading documentation
- Analyzing existing code
- Non-implementation tasks
Pre-Implementation Checklist
Before writing code, verify:
- Read
.claude/engineering-constitution.mdif unsure about any principle - Error handling: Am I adding retry logic? (NO - fail fast)
- Validation: Am I validating outside Zod schemas? (NO - API boundary only)
- Form defaults: Am I hardcoding in component? (NO - use
zodSchema.partial().parse({})) - TypeScript: Using
interfacefor objects,typefor unions? - Editing file: Am I fixing nearby issues? (YES - Boy Scout Rule)
Critical Rules
1. NO OVER-ENGINEERING (Most Violated)
Rule: No circuit breakers, retry logic, or graceful fallbacks. Fail fast.
Context: Pre-launch phase = velocity over resilience. We want loud failures, not silent degradation.
❌ FORBIDDEN PATTERNS:
// ❌ Circuit breaker
class CircuitBreaker {
state: 'OPEN' | 'CLOSED' | 'HALF-OPEN'
}
// ❌ Retry logic with exponential backoff
for (let i = 0; i < MAX_RETRIES; i++) {
try {
return await operation()
} catch (error) {
await sleep(Math.pow(2, i) * 100)
}
}
// ❌ Graceful fallbacks
try {
return await fetchData()
} catch {
return cachedData // Silent degradation
}
// ❌ Health monitoring
if (failureCount > threshold) {
activateCircuitBreaker()
}
✅ CORRECT PATTERN:
// ✅ Let it throw - operator sees error immediately
const data = await supabase.from('contacts').select()
// If 429 error occurs, it throws
// Operator investigates and fixes at source
Why Fail Fast:
- Complex error handling = maintenance burden
- No users yet = no one benefits from resilience
- Loud failures = immediate investigation
- Silent degradation = hidden problems
Common Rationalizations to REJECT:
- "This is for production" → We're pre-launch, velocity matters more
- "It needs to be resilient" → Resilience = fail loud, not graceful degradation
- "Users will see errors" → No users yet, operators need to see errors
- "Industry best practice" → Context matters, pre-launch has different needs
2. SINGLE COMPOSABLE ENTRY POINT
Rule: Have a single, composable entry point for data access (unifiedDataProvider), delegating to resource-specific modules. Validation via Zod schemas at API boundary (src/atomic-crm/validation/) ONLY.
❌ WRONG - Multiple Competing Entry Points:
// ❌ Direct API calls bypassing provider
import { contactsApi } from './contactsApi'
await contactsApi.updateContact(id, data)
// ❌ Validation in component
const isValidEmail = (email: string) => /@/.test(email)
// ❌ Which is authoritative? Now two+ definitions can diverge!
✅ CORRECT - Unified Entry Point with Delegation:
// Always use the unified provider
import { dataProvider } from '@/atomic-crm/providers/supabase/unifiedDataProvider'
await dataProvider.update('contacts', { id, data })
// Provider internally delegates to contacts-specific logic
// Validation: Zod at API boundary only
import { contactSchema } from '@/atomic-crm/validation/contacts'
Key Insight: This is the Composite pattern - one facade for the app to talk to, with resource-specific modules handling implementation details.
3. FORM STATE DERIVED FROM TRUTH
Rule: React Hook Form defaultValues MUST use zodSchema.partial().parse({}).
❌ WRONG - Hardcoded Defaults:
// ❌ Hardcoded in component
const form = useForm({
defaultValues: {
stage: 'new_lead', // Out of sync with schema!
priority: 'medium',
}
})
// ❌ Using defaultValue prop
<SelectInput source="stage" defaultValue="new_lead" />
✅ CORRECT - Schema-Derived Defaults:
// 1. Define defaults in Zod schema
export const opportunitySchema = z.object({
stage: z.string().default('new_lead'),
priority: z.string().default('medium'),
})
// 2. Extract defaults in component
const schemaDefaults = opportunitySchema.partial().parse({})
const form = useForm({
defaultValues: {
...schemaDefaults,
owner_id: identity.id, // Runtime values merged
}
})
// 3. NO defaultValue props on inputs
<SelectInput source="stage" /> // Uses form default
Why: Prevents drift between validation and UI.
3b. ZOD SECURITY PATTERNS (OWASP Compliant)
Rule: All Zod schemas at API boundary must follow security best practices.
String Length Limits (Prevent DoS):
// ✅ CORRECT: Always set max length
const schema = z.object({
name: z.string().max(100), // Names
title: z.string().max(200), // Titles/labels
description: z.string().max(2000), // Long text
url: z.string().url().max(2048), // URLs (browser limit)
});
// ❌ WRONG: No length limit = DoS vulnerability
const schema = z.object({
name: z.string(), // Attacker can send 10MB string
});
Strict Objects at API Boundary (Prevent Mass Assignment):
// ✅ CORRECT: Reject unknown keys at API boundary
export const createContactSchema = z.strictObject({
name: z.string().max(100),
email: z.string().email(),
}); // Unknown keys throw error
// ⚠️ INTERNAL ONLY: z.object() for composition/partial updates
const contactBaseSchema = z.object({
name: z.string().max(100),
}); // Can extend, allows unknown keys
Coercion for Form Inputs:
// ✅ CORRECT: HTML inputs return strings, coerce to type
const schema = z.object({
age: z.coerce.number().min(0).max(150),
price: z.coerce.number().positive(),
isActive: z.coerce.boolean(),
dueDate: z.coerce.date(),
});
// ❌ WRONG: Will fail on form input "42" (string, not number)
const schema = z.object({
age: z.number(), // z.number().parse("42") throws!
});
Allowlist Validation (OWASP):
// ✅ CORRECT: Allowlist with z.enum()
const stageSchema = z.enum(['new_lead', 'closed_won', 'closed_lost']);
const roleSchema = z.enum(['admin', 'manager', 'rep']);
// ❌ WRONG: Denylist (easily bypassed)
const badSchema = z.string().refine(
(val) => !val.includes('<script>'), // Attacker uses <SCRIPT>
'Invalid input'
);
4. BOY SCOUT RULE
Rule: Fix inconsistencies when editing files. Leave code better than you found it.
Examples:
- See unused import? Delete it
- See inconsistent spacing? Fix it
- See missing type? Add it
- See hardcoded value? Extract to constant
Scope: Only fix issues in files you're editing. Don't go on refactoring sprees.
5. TYPESCRIPT CONVENTIONS
Rule: interface for objects/classes, type for unions/intersections.
// ✅ Interfaces for object shapes
interface Contact {
id: string
first_name: string
}
// ✅ Types for unions
type Status = 'active' | 'inactive'
// ✅ Types for intersections
type ContactWithMeta = Contact & { created_at: string }
6. FORMS - USE REACT ADMIN COMPONENTS
Rule: Always use admin layer (src/components/admin/) for forms.
// ✅ React Admin components
import { TextInput, SelectInput } from 'react-admin'
// ❌ Raw HTML
<input type="text" />
7. COLORS - SEMANTIC VARIABLES ONLY
Rule: Use semantic CSS variables, never hex codes or direct OKLCH.
/* ✅ Semantic tokens */
color: var(--primary);
background: var(--brand-700);
/* ❌ Hex codes */
color: #7CB342;
/* ❌ Direct OKLCH */
color: oklch(65% 0.15 125);
Note: See atomic-crm-ui-design skill for complete color system guidance.
8. MIGRATIONS - TIMESTAMP FORMAT
Rule: Use Supabase CLI to generate correctly timestamped migrations.
# ✅ Correct
npx supabase migration new add_contact_tags
# Generates: 20250126143000_add_contact_tags.sql
# ❌ Don't manually create
# 001_add_contact_tags.sql
9. TAILWIND V4 CSS PATTERNS
9a. No @apply Self-Reference
Rule: Custom utilities in @layer utilities cannot @apply other custom utilities from the same layer.
Why: Tailwind v4 JIT processes utilities independently; self-references create circular dependencies.
❌ WRONG — fails in Tailwind v4:
@layer utilities {
.touch-target-44 { /* ... */ }
.data-cell {
@apply touch-target-44; /* ERROR: Cannot resolve */
}
}
✅ CORRECT — inline the styles:
@layer utilities {
.data-cell {
position: relative;
/* Inline the touch-target styles directly */
}
.data-cell::before {
content: '';
position: absolute;
top: calc((44px - 100%) / -2);
/* ... */
}
}
9b. Touch Expansion via calc()
Rule: Use calc((TARGET - 100%) / -2) to center-expand touch targets.
Why: 100% references parent height; dividing by -2 distributes expansion equally top/bottom.
Pattern:
.touch-target-44::before {
top: calc((44px - 100%) / -2); /* Expand upward */
bottom: calc((44px - 100%) / -2); /* Expand downward */
}
/* Result: 32px element → 44px touch area (6px each direction) */
9c. CSS Custom Properties for Typography Tokens
Rule: Define font tokens as CSS custom properties, not Tailwind theme extensions.
Why: Tailwind v4 JIT only bundles used utilities; custom properties are always available.
Pattern:
:root {
--text-table: 0.8125rem;
--text-table--line-height: 1.35;
}
/* Usage: text-[length:var(--text-table)] leading-[var(--text-table--line-height)] */
Common Mistakes
| Mistake | Fix |
|---|---|
| "Add retry logic for production" | NO. Fail fast. Pre-launch = velocity over resilience. |
| "Circuit breaker for resilience" | NO. Let errors throw. Investigate and fix at source. |
| Validation in component/utils | Move to Zod schema in src/atomic-crm/validation/ |
| Hardcoded form defaults | Use zodSchema.partial().parse({}) |
type for object shapes | Use interface for objects |
Raw <input> elements | Use React Admin's <TextInput> |
| Leaving unused imports | Fix when editing file (Boy Scout Rule) |
Implementation Workflow
1. Check Context
- Pre-launch = velocity over resilience
- Fail fast over graceful degradation
- Simple over clever
2. Verify Patterns
- Error handling: Let it throw
- Validation: Zod at API boundary only
- Forms: Defaults from schema
- Types:
interfacefor objects
3. Boy Scout Rule
- Fix issues in files you edit
- Don't go on tangential refactors
4. Commit
- Verify no retry/circuit breaker code
- Verify no validation outside Zod
- Verify form defaults from schema
Red Flags - STOP and Review
If you find yourself:
- Writing retry logic → Delete it, let errors throw
- Adding circuit breaker → Delete it, fail fast
- Creating "resilient" error handling → Pre-launch doesn't need it
- Validating outside Zod schemas → Move to API boundary
- Hardcoding form defaults → Use schema.partial().parse({})
- Using
<input>directly → Use React Admin components - Ignoring nearby issues → Fix them (Boy Scout Rule)
All of these mean: Review Engineering Constitution before proceeding.
Real-World Impact
Following Constitution:
- Fast feature velocity (no over-engineering)
- Loud failures = quick fixes
- Consistent data access (single composable entry point)
- No drift between UI and validation
- Clean codebase (Boy Scout Rule)
Violating Constitution:
- 3,000+ lines of retry/circuit breaker code
- Hidden failures (silent degradation)
- Multiple competing data access paths (bypassing unified provider)
- Form defaults out of sync
- Technical debt accumulation
Quick Reference Cards
Error Handling
| Situation | DO | DON'T |
|---|---|---|
| Database error | Let it throw | Retry logic |
| Bulk operations | Promise.allSettled() | Promise.all() |
| Validation error | Format for React Admin | Silent failure |
| User notification | Show specific message | Generic "Error" |
Validation
| Situation | DO | DON'T |
|---|---|---|
| Where to validate | src/atomic-crm/validation/ | Component/utils |
| Business defaults | .default() in Zod | Hardcode in component |
| Type inference | z.infer<typeof schema> | Manual type definition |
| Error format | { message, errors: {} } | Throw raw Zod error |
| String fields | .max(100) on ALL strings | No length limit |
| API boundary objects | z.strictObject() | z.object() (allows unknown keys) |
| Form number inputs | z.coerce.number() | z.number() (fails on string "42") |
| Form date inputs | z.coerce.date() | z.date() (fails on ISO strings) |
| Constrained values | z.enum(['a','b']) (allowlist) | Regex denylist |
Form State
| Situation | DO | DON'T |
|---|---|---|
| Form defaults | schema.partial().parse({}) | Hardcode in component |
| Array defaults | Sub-schema with .default() | defaultValue prop |
| JSONB arrays | ArrayInput + SimpleFormIterator | Manual array state |
| Submit transform | transform prop on CreateBase | Transform in component |
| Validation mode | mode: 'onSubmit' or 'onBlur' | mode: 'onChange' (perf issue) |
| Watch values | useWatch({ control, name }) | watch() (re-renders whole form) |
| RHF + resolver | zodResolver(schema) ONLY | Mix resolver + register validation |
Database
| Situation | DO | DON'T |
|---|---|---|
| New table | GRANT + RLS | RLS only |
| Role check | Helper function (is_admin()) | Inline check |
| Enum values | Add only (can't remove) | Try to remove |
| Migrations | npx supabase migration new | Manual numbering |
CSS/Tailwind v4
| Situation | DO | DON'T |
|---|---|---|
| Custom utility needs other utility | Inline the styles directly | @apply other-utility |
| Touch target expansion | calc((44px - 100%) / -2) | Fixed pixel offsets |
| Typography tokens | CSS custom properties | Tailwind theme extension |
| Arbitrary values | text-[length:var(--token)] | Hardcoded sizes |
Decision Tree: When This Skill Triggers
Starting implementation task?
│
├─ Handling errors?
│ └─ error-handling-basics.md → Fail fast (no retry)
│ error-handling-bulk.md → Promise.allSettled for bulk
│ error-handling-validation.md → Structured logging
│
├─ Adding validation?
│ └─ validation-basics.md → Zod at API boundary, core patterns
│ validation-arrays.md → JSONB arrays, sub-schemas
│ validation-schemas.md → Create/Update schemas
│ validation-advanced.md → Custom validators, transform
│ anti-patterns-validation.md → Avoid multiple sources
│
├─ Creating forms?
│ └─ form-defaults.md → Defaults from schema
│ form-arrays.md → JSONB array inputs
│ form-patterns.md → Tabbed forms, submission
│ form-advanced.md → Reset, debugging, conditional
│ anti-patterns-validation.md → Avoid hardcoded defaults
│
├─ Database changes?
│ └─ database-security.md → GRANT + RLS (two-layer)
│ database-migrations.md → Migration structure, enums
│ database-roles.md → Role-based permissions
│ database-advanced.md → Triggers, JSONB, views
│ database-reference.md → Decision tree, best practices
│ anti-patterns-database.md → Avoid common DB mistakes
│
├─ Security concerns?
│ └─ security-csv.md → CSV upload, formula injection
│ security-sql.md → SQL injection prevention
│ security-rls.md → RLS policies, authentication
│ security-xss.md → XSS prevention, URL validation
│
├─ Writing tests?
│ └─ testing-unit.md → Vitest, validation, components
│ testing-e2e.md → Playwright, critical journeys
│ testing-reference.md → Coverage, organization
│ anti-patterns-testing.md → Avoid testing implementation
│
└─ Unsure what to do?
└─ anti-patterns-engineering.md → Avoid over-engineering
error-handling-reference.md → Decision tree & rationalizations
Resource Files
Comprehensive patterns with real code examples from Atomic CRM:
Error Handling
- error-handling-basics.md - Fail-fast core patterns, forbidden retry/circuit breaker
- error-handling-bulk.md - Promise.allSettled for bulk operations
- error-handling-validation.md - Structured logging, Zod error formatting
- error-handling-reference.md - Decision tree, rationalizations, testing
Validation (Split for Focus)
- validation-basics.md - Core principles, basic schema, enum schemas
- validation-arrays.md - JSONB arrays, sub-schemas, superRefine
- validation-schemas.md - Create/Update schemas, error formatting
- validation-advanced.md - Custom validators, transform, import schemas
Form State Management (Split for Focus)
- form-defaults.md - Core principle, defaults from schema
- form-arrays.md - JSONB array inputs, SimpleFormIterator
- form-patterns.md - Tabbed forms, submission, error display
- form-advanced.md - Reset, debugging, conditional fields
Database (Split for Focus)
- database-security.md - GRANT + RLS two-layer security
- database-migrations.md - Migration structure, enum types
- database-roles.md - Role-based permissions, helper functions
- database-advanced.md - Triggers, JSONB, indexes, views
- database-reference.md - Decision tree, best practices
Security (Split for Focus)
- security-csv.md - CSV upload validation, formula injection, binary detection
- security-sql.md - SQL injection prevention, parameterized queries
- security-rls.md - RLS policies, authentication, role-based access
- security-xss.md - XSS prevention, URL validation, React escaping
Testing (Split for Focus)
- testing-unit.md - Vitest, validation testing, component testing, mocking
- testing-e2e.md - Playwright, critical journeys, accessibility
- testing-reference.md - Coverage, organization, database testing
Anti-Patterns (Split by Domain)
- anti-patterns-engineering.md - Over-engineering, circuit breakers, Promise.all
- anti-patterns-validation.md - Multiple validation sources, form defaults
- anti-patterns-database.md - RLS/GRANT, enums, migration verification
- anti-patterns-testing.md - Testing implementation details, error context
Constitution Principles Summary
- NO OVER-ENGINEERING - Fail fast, no retry/circuit breakers
- SINGLE COMPOSABLE ENTRY POINT - Unified data provider delegating to resource modules, Zod at API boundary
- BOY SCOUT RULE - Fix issues in files you edit
- VALIDATION - API boundary only (
src/atomic-crm/validation/) - FORM STATE - Derived from Zod schema (
.partial().parse({})) - TYPESCRIPT -
interfacefor objects,typefor unions - FORMS - React Admin components only
- COLORS - Semantic CSS variables only
- MIGRATIONS - Timestamp format via Supabase CLI
Zod Validation Rules (NEW - OWASP Compliant)
- STRING LIMITS - All strings must have
.max()constraint (100/200/2000 chars) - STRICT OBJECTS - Use
z.strictObject()at API boundary (prevents mass assignment) - COERCION - Use
z.coercefor all non-string form inputs (number, date, boolean) - ALLOWLIST - Use
z.enum()for constrained values (never denylist regex)
Form Performance Rules (NEW)
- VALIDATION MODE - Use
onSubmitoronBlurmode (neveronChange) - WATCH ISOLATION - Use
useWatch()for subscriptions (notwatch()) - RESOLVER ONLY - Use
zodResolver(schema)exclusively (don't mix withregistervalidation)
Tailwind v4 CSS Rules (NEW)
- NO @APPLY SELF-REFERENCE - Custom utilities cannot
@applyother custom utilities from same layer - TOUCH EXPANSION VIA CALC() - Use
calc((44px - 100%) / -2)for centered touch expansion - CSS CUSTOM PROPERTIES - Define typography tokens as CSS vars, not Tailwind theme extensions
Full details: docs/claude/engineering-constitution.md
Cross-Reference: See atomic-crm-ui-design skill for UI design patterns (colors, spacing, accessibility)
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon