スキル一覧に戻る
krwhynot

data-integrity-guards

by krwhynot

1🍴 0📅 2026年1月21日
GitHubで見るManusで実行

SKILL.md


name: data-integrity-guards description: Defense-in-depth validation pattern for Crispy CRM. Validates data at EVERY layer - API boundary (Zod with strictObject, .max() limits, coercion), form state (schema defaults, useWatch), database (RLS/soft-delete). Triggers on validation, data flow, integrity, security, guards, layers, Zod, RLS, form defaults, strictObject, string limits, coerce, allowlist, mass assignment, DoS.

Data Integrity Guards

Core Concept

Validate at EVERY layer data passes through. Make bugs structurally impossible.

This pattern addresses data corruption and validation gaps by implementing checks across multiple system layers rather than relying on a single checkpoint.

The Four-Layer Framework for Crispy CRM

Layer 1: API Boundary (Zod Validation)

Where: unifiedDataProvider.ts via ValidationService Purpose: Reject invalid input before database operations; prevent security vulnerabilities

// ✓ CORRECT: Validation at API boundary only
// src/atomic-crm/providers/supabase/services/ValidationService.ts
export class ValidationService {
  async validate(resource: string, method: string, data: unknown): Promise<void> {
    const validator = this.validationRegistry[resource];
    if (method === "create" && validator.create) {
      await validator.create(data);  // Zod validation HERE
    }
  }
}

// ✗ WRONG: Validation in form component
const ContactCreate = () => {
  const validate = (values) => { /* DON'T DO THIS */ };
  return <SimpleForm validate={validate}>...</SimpleForm>;
};

Security Requirements (OWASP Compliant):

RequirementImplementationRisk if Missing
String limits.max(100) on ALL stringsDoS via 10MB payloads
Strict objectsz.strictObject() for create/updateMass assignment attacks
Coercionz.coerce.number() for form inputsType validation failures
Allowlistz.enum(['a','b']) for optionsDenylist bypass
// ✓ CORRECT: Secure schema at API boundary
export const createContactSchema = z.strictObject({
  name: z.string().min(1).max(100),        // Length limit
  email: z.string().email().max(254),       // Email limit
  age: z.coerce.number().min(0).max(150),   // Coerced from form
  role: z.enum(['admin', 'user', 'guest']), // Allowlist
}); // strictObject rejects unknown keys

// ✗ WRONG: Vulnerable schema
const badSchema = z.object({
  name: z.string(),  // No max = DoS risk
  role: z.string(),  // No enum = injection risk
}); // object allows unknown keys = mass assignment

Key Files:

  • src/atomic-crm/validation/*.ts - Zod schemas per resource
  • src/atomic-crm/providers/supabase/services/ValidationService.ts - Central validation

Layer 2: Form State Defaults (Schema-Derived)

Where: Form components using zodSchema.partial().parse({}) Purpose: Ensure form state starts valid, preventing null pointer errors

// ✓ CORRECT: Form defaults from Zod schema
import { contactBaseSchema } from '../validation/contacts';

const ContactCreate = () => {
  const defaultValues = contactBaseSchema.partial().parse({});
  return (
    <Create>
      <SimpleForm defaultValues={defaultValues}>
        <TextInput source="first_name" />
      </SimpleForm>
    </Create>
  );
};

// ✗ WRONG: Hardcoded defaults that can drift from schema
const ContactCreate = () => {
  const defaultValues = {
    first_name: '',
    email: []  // May not match schema structure!
  };
};

Pattern: Base schemas are EXPORTED specifically for form default generation:

// src/atomic-crm/validation/contacts.ts
// EXPORTED: Enables form default generation via contactBaseSchema.partial().parse({})
// per Engineering Constitution #5: FORM STATE DERIVED FROM TRUTH
export const contactBaseSchema = z.object({...});

Layer 3: Database Guards (RLS + Soft Deletes)

Where: Supabase RLS policies, migration files Purpose: Prevent unauthorized access and preserve data for audit

-- RLS Policy: Users can only see their organization's data
CREATE POLICY "Users can view own org contacts"
  ON contacts FOR SELECT
  USING (organization_id IN (
    SELECT organization_id FROM user_organizations
    WHERE user_id = auth.uid()
  ));

-- Soft delete: Never truly delete, preserve for audit
-- ✓ CORRECT: Use deleted_at column
UPDATE contacts SET deleted_at = NOW() WHERE id = $1;

-- ✗ WRONG: Hard delete loses audit trail
DELETE FROM contacts WHERE id = $1;

Key Patterns:

  • All tables have deleted_at column for soft deletes
  • supportsSoftDelete() utility checks if resource uses soft delete
  • RLS policies enforce multi-tenant isolation

Layer 4: Debug Instrumentation (Logging)

Where: src/lib/logger.ts, src/lib/devLogger.ts Purpose: Capture forensic context when other layers fail

// Structured logging for production (Sentry integration)
import { logger } from "@/lib/logger";

try {
  await validateContactForm(data);
} catch (error) {
  logger.error("Contact validation failed", {
    context: {
      resource: "contacts",
      method: "create",
      data: sanitize(data)  // Remove PII
    },
    error
  });
  throw error;  // Still fail fast!
}

// Development logging (stripped in production)
import { devLog, DEV } from "@/lib/devLogger";

if (DEV) {
  devLog("Validating contact", data);
}

When to Apply This Pattern

Checklist Before Writing Data Operations

  1. API Boundary - Is there a Zod schema in validation/*.ts?
  2. Form Defaults - Does the form use schema.partial().parse({})?
  3. Database Layer - Are RLS policies in place? Soft delete configured?
  4. Logging - Will failures be captured for debugging?

Red Flags (Validation Gaps)

Red FlagFix
validate={} prop on formRemove, use API boundary validation
defaultValue="..." hardcodedUse schema.partial().parse({})
DELETE FROM tableUse UPDATE SET deleted_at = NOW()
No RLS policy on tableAdd policy in migration
Silent catch blockAdd logger.error() before re-throw
z.string() without .max()Add .max(100) for names, .max(2000) for text
z.object() at API boundaryUse z.strictObject() for create/update schemas
z.number() for form inputUse z.coerce.number() (forms return strings)
z.string().refine(!includes) denylistUse z.enum([...]) allowlist instead
mode: 'onChange' in useFormUse mode: 'onSubmit' or 'onBlur' (performance)
watch() at form rootUse useWatch({ name }) for isolated re-renders

Real Examples from Crispy CRM

Contact Validation (All 4 Layers)

Layer 1 - API Boundary:

// src/atomic-crm/validation/contacts.ts
export const contactSchema = contactBaseSchema
  .transform(transformContactData)
  .superRefine((data, ctx) => {
    if (!data.name && !data.first_name && !data.last_name) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["name"],
        message: "Either name or first_name/last_name must be provided",
      });
    }
  });

Layer 2 - Form Defaults:

// Form component using schema-derived defaults
const defaultValues = contactBaseSchema.partial().parse({});
// Returns: { email: [], phone: [], ... } with correct structure

Layer 3 - Database:

-- Migration: 20251129030358_contact_organization_id_not_null.sql
-- Enforces business rule: Contacts must belong to organization
ALTER TABLE contacts
  ALTER COLUMN organization_id SET NOT NULL;

Layer 4 - Logging:

// unifiedDataProvider.ts
logger.error("Create operation failed", {
  context: { resource, data: params.data },
  error
});

Integration with Engineering Constitution

This skill reinforces these Constitution principles:

PrincipleHow Data Integrity Guards Help
Fail FastValidation throws immediately, no silent failures
Single Source of TruthZod schemas define validation once
Form State from Schemaschema.partial().parse({}) pattern
No Retry LogicValidation errors fail immediately

Quick Reference

┌─────────────────────────────────────────────────────────────┐
│                    DATA INTEGRITY LAYERS                     │
├─────────────────────────────────────────────────────────────┤
│  Layer 1: API BOUNDARY                                      │
│  └─ ValidationService.validate() → Zod schemas              │
│                                                             │
│  Layer 2: FORM STATE                                        │
│  └─ schema.partial().parse({}) → Safe defaults              │
│                                                             │
│  Layer 3: DATABASE                                          │
│  └─ RLS policies + deleted_at → Security + Audit            │
│                                                             │
│  Layer 4: INSTRUMENTATION                                   │
│  └─ logger.error() → Forensic context                       │
└─────────────────────────────────────────────────────────────┘

See Also

  • src/atomic-crm/validation/ - All Zod schemas
  • src/atomic-crm/providers/supabase/services/ValidationService.ts - Central validation
  • supabase/migrations/ - RLS policies and schema constraints
  • src/lib/logger.ts - Structured logging

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です