Volume 3: Human-System Collaboration

Pattern 9: Contextual Constraints

Part II: Interaction Patterns - Intelligence Patterns


Opening Scenario: The Date Field That Couldn't Decide

David was building a scheduling system for a medical practice. He started with what seemed like a simple field:

<label>Date:</label>
<input type="date" name="appointmentDate">

The practice manager, Linda, tested it and immediately found problems.

"David, I'm trying to schedule Mr. Johnson's follow-up appointment for next Tuesday, but the system won't let me select it."

David looked at his validation code:

function validateDate(date) {
  const selected = new Date(date);
  const today = new Date();

  // Date must be in the past
  if (selected > today) {
    return { valid: false, message: "Date cannot be in the future" };
  }

  return { valid: true };
}

"Oh," David said. "I wrote this for the patient intake form, where we ask for birth date. Birth dates have to be in the past. But you're scheduling an appointment, which has to be in the future."

"There's more," Linda said. She showed him the medical history form. "When documenting a patient's previous surgery, I need to enter a date from years ago. But when scheduling a lab test, I can only book certain days based on what tests the lab runs."

David realized: the same field - a date - had completely different constraints depending on context:

Birth date: Must be in the past, can't be future, reasonably within human lifespan Appointment date: Must be in the future, can't be past, only on business days, only when doctor available Medical history date: Any date in the past, but not before patient was born Lab test date: Must be in the future, only specific days (lab runs different tests different days), no more than 90 days out Prescription date: Today or recent past (within 30 days for controlled substances) Insurance effective date: Could be past, present, or future, but must align with policy period


Six months later, David showed me the redesigned system:

class ContextualDateValidator {
  validate(date, context) {
    const constraints = this.getConstraints(context);
    const selected = new Date(date);
    const today = new Date();

    // Apply contextual constraints
    const results = [];

    // Temporal direction
    if (constraints.mustBeFuture && selected <= today) {
      return {
        valid: false,
        message: `${context.fieldLabel} must be in the future`,
        suggestion: this.getNextValidDate(context)
      };
    }

    if (constraints.mustBePast && selected >= today) {
      return {
        valid: false,
        message: `${context.fieldLabel} must be in the past`
      };
    }

    // Business rules
    if (constraints.businessDaysOnly && this.isWeekend(selected)) {
      return {
        valid: false,
        message: "Please select a business day (Monday-Friday)",
        suggestion: this.getNextBusinessDay(selected)
      };
    }

    // Availability constraints
    if (constraints.checkAvailability) {
      const available = this.checkResourceAvailability(
        selected,
        context.resourceId,
        context.resourceType
      );

      if (!available.isAvailable) {
        return {
          valid: false,
          message: available.reason,
          suggestions: available.alternativeDates
        };
      }
    }

    // Domain-specific constraints
    if (constraints.labTestDay) {
      const validDays = this.getLabTestDays(context.testType);
      const dayOfWeek = selected.getDay();

      if (!validDays.includes(dayOfWeek)) {
        return {
          valid: false,
          message: `${context.testType} is only available on ${this.formatDays(validDays)}`,
          suggestions: this.getNextValidLabDays(selected, validDays)
        };
      }
    }

    // Relative constraints (compared to other dates)
    if (constraints.mustBeAfter) {
      const compareDate = new Date(context[constraints.mustBeAfter]);
      if (selected <= compareDate) {
        return {
          valid: false,
          message: `Must be after ${constraints.mustBeAfter} (${this.formatDate(compareDate)})`
        };
      }
    }

    return { valid: true };
  }

  getConstraints(context) {
    const constraintMap = {
      'birthDate': {
        mustBePast: true,
        maxYearsAgo: 120,
        minYearsAgo: 0,
        businessDaysOnly: false
      },

      'appointmentDate': {
        mustBeFuture: true,
        businessDaysOnly: true,
        checkAvailability: true,
        resourceType: 'provider',
        minDaysOut: 0,
        maxDaysOut: 365
      },

      'labTestDate': {
        mustBeFuture: true,
        labTestDay: true,
        testType: context.testType,
        maxDaysOut: 90
      },

      'medicalHistoryDate': {
        mustBePast: true,
        mustBeAfter: 'birthDate',
        maxYearsAgo: null  // Any date after birth
      },

      'prescriptionDate': {
        allowToday: true,
        maxDaysAgo: 30,  // Controlled substances
        mustNotBeFuture: true
      }
    };

    return constraintMap[context.fieldType] || {};
  }
}

Now the same date field had intelligent, context-aware constraints. The form knew: - WHO was using it (doctor can backdate prescriptions within limits, nurse cannot) - WHAT it was for (appointment vs medical history vs lab test) - WHEN it was being used (relative to other dates in the form) - WHY the date was needed (different policies for different purposes)

David's system went from rigid, one-size-fits-all validation to flexible, intelligent constraints that adapted to context.

Context

Contextual Constraints applies when:

Same field, different rules: Validation depends on purpose, user, or situation

Context is knowable: System has enough information to determine appropriate constraints

User role matters: Different users have different permissions and requirements

Purpose varies: Same data type used for different reasons

Temporal context relevant: When something happens affects what's valid

Relationships matter: Constraints depend on other fields or data

Business rules complex: Simple validation insufficient for real-world requirements

Problem Statement

Most validation applies the same rules regardless of context:

Ignoring user role:

// Bad: Same amount limit for everyone
function validateExpenseAmount(amount) {
  return amount <= 1000;  // $1,000 limit
}

// Staff can expense up to $500
// Managers can expense up to $5,000
// Executives can expense up to $25,000
// But system treats everyone the same!

Missing purpose:

// Bad: Email always required
function validateEmail(email) {
  if (!email) {
    return { valid: false, message: "Email is required" };
  }
  return { valid: true };
}

// But:
// - Required for account creation
// - Optional for guest checkout
// - Not needed for in-person registration

No temporal awareness:

// Bad: Static date validation
function validateDate(date) {
  const d = new Date(date);
  return d > new Date('1900-01-01') && d < new Date('2100-12-31');
}

// Accepts birth dates in the future
// Accepts appointment dates in the past
// No awareness of context

Ignoring relationships:

// Bad: Independent field validation
function validateEndDate(endDate) {
  return endDate instanceof Date && !isNaN(endDate);
}

// Doesn't check that end date is after start date
// Doesn't verify duration is reasonable
// No relationship awareness

One-size-fits-all:

// Bad: Same phone validation everywhere
function validatePhone(phone) {
  return /^\d{10}$/.test(phone);
}

// But:
// - Personal phone: US format
// - International client: various formats
// - Fax number: may include extension
// - Emergency contact: require area code

We need constraints that adapt based on who is using the form, what they're trying to do, when they're doing it, and why.

Forces

Flexibility vs Consistency

  • Context-aware rules provide appropriate validation
  • But too much variation confuses users
  • Balance adaptation with predictability

Complexity vs Maintainability

  • Contextual logic can become complex
  • Many if/else branches hard to maintain
  • Need systematic approach to context handling

Security vs Usability

  • More permissive rules for trusted users
  • But can't compromise security
  • Balance convenience with protection

Explicit vs Implicit Context

  • Some context is obvious (user role)
  • Some must be inferred (purpose from workflow)
  • Decision: ask user or detect automatically?

Performance vs Freshness

  • Checking availability is slow
  • Cached rules may be stale
  • Balance speed with accuracy

Solution

Define validation constraints that adapt based on context dimensions (who, what, when, where, why), with clear priority rules for resolving conflicts and transparent explanations for users.

The pattern has four key context dimensions:

1. Actor Context (WHO)

Constraints based on user identity, role, and permissions:

class ActorContext {
  getConstraints(fieldType, user) {
    const roleConstraints = {
      'staff': {
        expenseLimit: 500,
        approvalRequired: true,
        canBackdate: false,
        editWindow: 7  // days
      },

      'manager': {
        expenseLimit: 5000,
        approvalRequired: false,
        canBackdate: 7,  // days
        editWindow: 30
      },

      'executive': {
        expenseLimit: 25000,
        approvalRequired: false,
        canBackdate: 30,
        editWindow: 90
      },

      'finance': {
        expenseLimit: null,  // No limit
        approvalRequired: false,
        canBackdate: 365,
        editWindow: null  // Unlimited
      }
    };

    const baseConstraints = roleConstraints[user.role] || roleConstraints['staff'];

    // Apply additional constraints based on user attributes
    if (user.isTraining) {
      return {
        ...baseConstraints,
        expenseLimit: Math.min(baseConstraints.expenseLimit, 100),
        approvalRequired: true
      };
    }

    if (user.hasViolations) {
      return {
        ...baseConstraints,
        approvalRequired: true,
        editWindow: 0  // No editing
      };
    }

    return baseConstraints;
  }
}

2. Purpose Context (WHAT & WHY)

Constraints based on what the data will be used for:

class PurposeContext {
  getConstraints(fieldType, purpose) {
    const purposeConstraints = {
      'email': {
        'account_creation': {
          required: true,
          mustVerify: true,
          allowDisposable: false,
          corporateOnly: false
        },

        'guest_checkout': {
          required: false,
          mustVerify: false,
          allowDisposable: true,
          corporateOnly: false
        },

        'employee_onboarding': {
          required: true,
          mustVerify: true,
          allowDisposable: false,
          corporateOnly: true  // Must be company domain
        },

        'newsletter_signup': {
          required: true,
          mustVerify: false,
          allowDisposable: true,
          corporateOnly: false
        }
      },

      'phone': {
        'emergency_contact': {
          required: true,
          mustBeDifferent: true,  // Can't be user's own number
          allowInternational: true,
          verifyCarrier: false
        },

        'appointment_reminder': {
          required: false,
          mustBeMobile: true,  // For SMS
          allowLandline: false,
          verifyCarrier: true
        },

        'billing_contact': {
          required: true,
          mustBeMobile: false,
          allowLandline: true,
          verifyCarrier: false
        }
      }
    };

    return purposeConstraints[fieldType]?.[purpose] || {};
  }
}

3. Temporal Context (WHEN)

Constraints based on timing and relationships to other dates:

class TemporalContext {
  getConstraints(fieldType, temporalContext) {
    const now = new Date();

    const constraints = {
      'appointmentDate': {
        direction: 'future',
        minDaysFromNow: temporalContext.urgency === 'routine' ? 1 : 0,
        maxDaysFromNow: 365,
        businessDaysOnly: true,
        excludeHolidays: true,
        checkAvailability: true
      },

      'birthDate': {
        direction: 'past',
        minYearsAgo: 0,
        maxYearsAgo: 120,
        mustBeBefore: now
      },

      'hireDate': {
        direction: 'either',  // Can be past or future
        mustBeAfter: 'birthDate',
        mustNotExceed: { years: 16, from: 'birthDate' },  // Must be 16+
        businessDaysOnly: true
      },

      'effectiveDate': {
        direction: 'either',
        mustBeWithin: {
          field: 'policyPeriod',
          type: 'range'
        }
      }
    };

    return constraints[fieldType] || {};
  }

  validateTemporal(date, constraints, relatedDates = {}) {
    const selected = new Date(date);
    const now = new Date();

    // Direction constraints
    if (constraints.direction === 'future' && selected <= now) {
      return {
        valid: false,
        message: "Date must be in the future"
      };
    }

    if (constraints.direction === 'past' && selected >= now) {
      return {
        valid: false,
        message: "Date must be in the past"
      };
    }

    // Relative constraints
    if (constraints.mustBeAfter) {
      const compareDate = relatedDates[constraints.mustBeAfter];
      if (compareDate && selected <= new Date(compareDate)) {
        return {
          valid: false,
          message: `Must be after ${constraints.mustBeAfter}`
        };
      }
    }

    // Duration constraints
    if (constraints.mustNotExceed) {
      const fromDate = new Date(relatedDates[constraints.mustNotExceed.from]);
      const yearsDiff = (selected - fromDate) / (365.25 * 24 * 60 * 60 * 1000);

      if (yearsDiff < constraints.mustNotExceed.years) {
        return {
          valid: false,
          message: `Must be at least ${constraints.mustNotExceed.years} years after ${constraints.mustNotExceed.from}`
        };
      }
    }

    return { valid: true };
  }
}

4. Situational Context (WHERE & Compound)

Constraints based on location, workflow state, and combined factors:

class SituationalContext {
  getConstraints(fieldType, situation) {
    // Geographic context
    if (situation.location) {
      return this.getGeographicConstraints(fieldType, situation.location);
    }

    // Workflow state context
    if (situation.workflowState) {
      return this.getWorkflowConstraints(fieldType, situation.workflowState);
    }

    // Compound context
    return this.getCompoundConstraints(fieldType, situation);
  }

  getGeographicConstraints(fieldType, location) {
    if (fieldType === 'phone') {
      const formats = {
        'US': { pattern: /^\d{10}$/, format: '(XXX) XXX-XXXX' },
        'UK': { pattern: /^\d{11}$/, format: 'XXXXX XXXXXX' },
        'FR': { pattern: /^\d{10}$/, format: 'XX XX XX XX XX' }
      };

      return formats[location.country] || formats['US'];
    }

    if (fieldType === 'postalCode') {
      const formats = {
        'US': { pattern: /^\d{5}(-\d{4})?$/, name: 'ZIP code' },
        'CA': { pattern: /^[A-Z]\d[A-Z] \d[A-Z]\d$/, name: 'Postal code' },
        'UK': { pattern: /^[A-Z]{1,2}\d{1,2} \d[A-Z]{2}$/, name: 'Postcode' }
      };

      return formats[location.country] || {};
    }
  }

  getWorkflowConstraints(fieldType, workflowState) {
    // Different constraints based on where in the workflow
    const stateConstraints = {
      'draft': {
        required: false,
        canEdit: true,
        validationLevel: 'warning'
      },

      'submitted': {
        required: true,
        canEdit: false,
        validationLevel: 'error'
      },

      'approved': {
        required: true,
        canEdit: false,
        validationLevel: 'error',
        auditTrail: true
      }
    };

    return stateConstraints[workflowState] || stateConstraints['draft'];
  }

  getCompoundConstraints(fieldType, situation) {
    // Combine multiple context factors
    const constraints = {};

    // Example: Shipping address validation
    if (fieldType === 'shippingAddress') {
      constraints.required = situation.orderType !== 'digital';
      constraints.international = situation.customerType === 'international';
      constraints.verifyWithCarrier = situation.orderValue > 1000;
      constraints.requireSignature = situation.orderValue > 5000;
    }

    return constraints;
  }
}

Implementation Details

Complete Contextual Constraint Engine

class ContextualConstraintEngine {
  constructor() {
    this.actorContext = new ActorContext();
    this.purposeContext = new PurposeContext();
    this.temporalContext = new TemporalContext();
    this.situationalContext = new SituationalContext();
  }

  async validate(field, value, context) {
    // Gather constraints from all context dimensions
    const constraints = await this.gatherConstraints(field, context);

    // Apply constraints in priority order
    const results = await this.applyConstraints(value, constraints, context);

    // Return combined result
    return this.combineResults(results, constraints);
  }

  async gatherConstraints(field, context) {
    const constraints = {
      actor: this.actorContext.getConstraints(field.type, context.user),
      purpose: this.purposeContext.getConstraints(field.type, context.purpose),
      temporal: this.temporalContext.getConstraints(field.type, context.temporal),
      situational: this.situationalContext.getConstraints(field.type, context.situation)
    };

    // Merge constraints with priority rules
    return this.mergeConstraints(constraints, field.type);
  }

  mergeConstraints(constraints, fieldType) {
    // Priority order: actor > purpose > temporal > situational
    // More restrictive wins when conflicts

    const merged = {};

    // Start with least restrictive (situational)
    Object.assign(merged, constraints.situational);

    // Layer on temporal
    Object.assign(merged, constraints.temporal);

    // Layer on purpose
    Object.assign(merged, constraints.purpose);

    // Layer on actor (highest priority)
    Object.assign(merged, constraints.actor);

    // Resolve conflicts (take most restrictive)
    if (merged.expenseLimit !== undefined && constraints.purpose.expenseLimit !== undefined) {
      merged.expenseLimit = Math.min(
        merged.expenseLimit || Infinity,
        constraints.purpose.expenseLimit || Infinity
      );
    }

    if (merged.required !== undefined) {
      // If any context says required, it's required
      merged.required = 
        constraints.actor.required ||
        constraints.purpose.required ||
        constraints.situational.required;
    }

    return merged;
  }

  async applyConstraints(value, constraints, context) {
    const results = [];

    // Required check
    if (constraints.required && !value) {
      results.push({
        valid: false,
        constraint: 'required',
        message: this.getRequiredMessage(constraints, context),
        severity: 'error'
      });
      return results;  // Stop if required and empty
    }

    // Type-specific validation
    if (value) {
      // Amount constraints
      if (constraints.minAmount !== undefined && value < constraints.minAmount) {
        results.push({
          valid: false,
          constraint: 'minAmount',
          message: `Amount must be at least ${this.formatCurrency(constraints.minAmount)}`,
          severity: 'error'
        });
      }

      if (constraints.maxAmount !== undefined && value > constraints.maxAmount) {
        results.push({
          valid: false,
          constraint: 'maxAmount',
          message: `Amount cannot exceed ${this.formatCurrency(constraints.maxAmount)}`,
          severity: 'error',
          explanation: this.getExplanation('maxAmount', constraints, context)
        });
      }

      // Approval constraints
      if (constraints.requiresApproval && value > constraints.approvalThreshold) {
        results.push({
          valid: true,
          constraint: 'requiresApproval',
          message: 'This amount requires manager approval',
          severity: 'info'
        });
      }
    }

    return results;
  }

  getRequiredMessage(constraints, context) {
    if (context.purpose === 'account_creation') {
      return 'Email is required to create an account';
    }

    if (context.purpose === 'emergency_contact') {
      return 'Emergency contact phone is required for patient safety';
    }

    return 'This field is required';
  }

  getExplanation(constraint, constraints, context) {
    const explanations = {
      'maxAmount': () => {
        if (context.user.role === 'staff') {
          return `Staff members can expense up to ${this.formatCurrency(constraints.maxAmount)}. Amounts above this require manager approval.`;
        }
        return null;
      },

      'mustBeFuture': () => {
        if (context.purpose === 'appointment') {
          return 'Appointments must be scheduled for future dates. For backdating, contact your supervisor.';
        }
        return null;
      }
    };

    const explainer = explanations[constraint];
    return explainer ? explainer() : null;
  }

  combineResults(results, constraints) {
    const errors = results.filter(r => !r.valid && r.severity === 'error');

    if (errors.length > 0) {
      return {
        valid: false,
        message: errors[0].message,
        explanation: errors[0].explanation,
        allErrors: errors
      };
    }

    const warnings = results.filter(r => r.severity === 'warning');
    const info = results.filter(r => r.severity === 'info');

    return {
      valid: true,
      warnings: warnings,
      info: info,
      appliedConstraints: constraints
    };
  }
}

// Usage
const engine = new ContextualConstraintEngine();

const result = await engine.validate(
  { type: 'expenseAmount', name: 'amount' },
  1500,
  {
    user: { role: 'staff', id: 'user123' },
    purpose: 'travel_expense',
    temporal: { dateIncurred: '2024-12-20' },
    situation: { workflowState: 'draft' }
  }
);

if (!result.valid) {
  console.log(result.message);
  if (result.explanation) {
    console.log(result.explanation);
  }
}

Consequences

Benefits

Appropriate Validation: - Rules match the actual requirement - Users not blocked by overly restrictive validation - Security maintained where needed

Better User Experience: - Constraints make sense in context - Clear explanations for restrictions - Helpful suggestions for alternatives

Encodes Business Rules: - Complex policies implemented correctly - Role-based permissions enforced - Compliance requirements met

Reduces Errors: - Context-appropriate defaults - Catches mistakes early - Prevents invalid combinations

Flexibility: - Same field serves multiple purposes - Adapts to changing requirements - Scales with organizational complexity

Liabilities

Implementation Complexity: - Many conditional branches - Context must be gathered and passed - Priority rules can conflict

Testing Challenges: - Must test all context combinations - Edge cases multiply - Regression testing complex

Performance Concerns: - Checking availability is slow - Multiple validation layers add latency - Caching helps but adds complexity

User Confusion: - Same field, different rules can surprise - Need clear communication - Transparency essential

Maintenance Burden: - Business rules change - Context logic grows over time - Documentation critical

Domain Examples

Healthcare: Appointment Scheduling

const appointmentConstraints = {
  actor: (user) => ({
    canSchedule: ['front_desk', 'nurse', 'doctor'].includes(user.role),
    canBackdate: user.role === 'doctor',
    maxDaysOut: user.role === 'doctor' ? 365 : 90
  }),

  purpose: (appointmentType) => ({
    'new_patient': {
      minDuration: 60,  // minutes
      requiresSpecialty: true,
      businessDaysOnly: true
    },
    'follow_up': {
      minDuration: 30,
      requiresSpecialty: false,
      businessDaysOnly: false  // Can book weekends for urgent follow-up
    },
    'urgent': {
      minDuration: 15,
      sameDayAllowed: true,
      overrideAvailability: true
    }
  }),

  temporal: (context) => ({
    minDaysFromNow: context.appointmentType === 'urgent' ? 0 : 1,
    maxDaysFromNow: 365,
    excludeDates: context.providerVacation,
    mustBeAfter: context.lastVisit ? addDays(context.lastVisit, 7) : null
  })
};

Financial: Transaction Limits

const transactionConstraints = {
  actor: (user) => ({
    dailyLimit: {
      'customer': 5000,
      'business': 25000,
      'premium': 100000
    }[user.accountType],

    requiresVerification: user.accountAge < 30,  // days
    requiresTwoFactor: user.riskScore > 50
  }),

  purpose: (transactionType) => ({
    'wire_transfer': {
      minAmount: 100,
      maxAmount: 50000,
      businessDaysOnly: true,
      cutoffTime: '15:00'  // 3 PM
    },
    'ach_transfer': {
      minAmount: 1,
      maxAmount: 25000,
      processingDays: 3
    },
    'bill_payment': {
      minAmount: 1,
      maxAmount: 10000,
      allowScheduled: true
    }
  }),

  temporal: (context) => ({
    canBackdate: false,
    maxFutureDate: addDays(new Date(), 365),
    excludeWeekends: context.transactionType === 'wire_transfer',
    excludeHolidays: true
  })
};
const filingConstraints = {
  actor: (user) => ({
    canFile: ['attorney', 'paralegal', 'clerk'].includes(user.role),
    canWithdraw: user.role === 'attorney',
    canAmend: user.barNumber ? true : false,
    editWindow: {
      'attorney': 24,  // hours
      'paralegal': 2,
      'clerk': 0  // Cannot edit after filing
    }[user.role]
  }),

  purpose: (filingType) => ({
    'complaint': {
      required: ['jurisdiction', 'parties', 'claims'],
      maxPageLength: null,
      allowsAttachments: true,
      filingFee: 400
    },
    'motion': {
      required: ['jurisdiction', 'case_number', 'relief_sought'],
      maxPageLength: 25,
      requiresBrief: true,
      filingFee: 0
    },
    'discovery': {
      required: ['case_number', 'discovery_type'],
      maxPageLength: null,
      deadlineRelative: 'trial_date',
      daysBefore: 30
    }
  }),

  temporal: (context) => ({
    mustBeWithinStatute: context.filingType === 'complaint',
    statuteDays: context.jurisdiction === 'federal' ? 90 : 120,
    businessDaysOnly: true,
    courtHoursOnly: { start: '08:00', end: '16:00' },
    excludeCourtHolidays: true
  })
};

E-commerce: Shipping Address

const shippingConstraints = {
  actor: (user) => ({
    canShipInternational: user.accountType !== 'basic',
    expeditedAvailable: user.accountType === 'premium',
    freeShippingThreshold: {
      'basic': 50,
      'plus': 35,
      'premium': 0
    }[user.accountType]
  }),

  purpose: (orderContext) => ({
    required: orderContext.hasPhysicalItems,
    verifyWithCarrier: orderContext.orderValue > 1000,
    requireSignature: orderContext.orderValue > 5000 || orderContext.containsRegulated,
    allowPOBox: !orderContext.requiresSignature && !orderContext.containsPerishable
  }),

  situational: (context) => ({
    domesticOnly: context.itemCategory === 'hazmat',
    restrictions: context.state === 'HI' || context.state === 'AK' ? ['oversized', 'perishable'] : [],
    deliveryDays: {
      'standard': context.inState ? 3 : 7,
      'expedited': context.inState ? 1 : 3,
      'international': 10
    }
  })
};

Education: Course Registration

const registrationConstraints = {
  actor: (student) => ({
    canRegister: student.accountStatus === 'active' && student.balanceDue === 0,
    maxCredits: {
      'full_time': 18,
      'part_time': 9,
      'graduate': 12
    }[student.enrollment],

    earlyRegistration: student.gpa >= 3.5 || student.seniority === 'senior',
    waitlistPriority: student.major === 'course_department'
  }),

  purpose: (courseContext) => ({
    prerequisitesMet: courseContext.prerequisites.every(p => 
      student.completedCourses.includes(p)
    ),

    corequisitesConcurrent: courseContext.corequisites.every(c =>
      student.currentCourses.includes(c) || student.selectedCourses.includes(c)
    ),

    majorRestriction: courseContext.restrictedToMajors ? 
      courseContext.allowedMajors.includes(student.major) : true
  }),

  temporal: (context) => ({
    registrationWindow: {
      start: context.earlyRegistration ? 
        new Date('2025-01-15') : new Date('2025-01-22'),
      end: new Date('2025-02-01')
    },

    dropDeadline: new Date('2025-02-15'),
    withdrawDeadline: new Date('2025-04-15'),
    canAddAfterStart: context.courseStarted ? 7 : null  // days
  })
};

Prerequisites: - Volume 3, Pattern 6: Domain-Aware Validation (provides validation logic) - Volume 3, Pattern 7: Calculated Dependencies (calculated values affect constraints)

Synergies: - Volume 3, Pattern 8: Intelligent Defaults (defaults respect constraints) - Volume 3, Pattern 10: Semantic Suggestions (suggest values that meet constraints) - Volume 3, Pattern 14: Cross-Field Validation (constraints often span fields)

Conflicts: - Overly complex rules (when to simplify vs add context) - User confusion (same field, different rules)

Alternatives: - Multiple separate forms (instead of one adaptive form) - Explicit mode selection (user chooses context explicitly) - Progressive constraints (relax over time)

Known Uses

Salesforce: Field-level security and validation rules vary by profile and record type

Healthcare Systems (Epic, Cerner): Appointment scheduling constraints based on provider, type, and insurance

Banking Apps: Transaction limits vary by account type, verification status, and transaction type

Travel Booking (Expedia, Kayak): Date constraints based on travel type (one-way, round-trip, multi-city)

HR Systems (Workday, BambooHR): PTO request constraints based on role, tenure, and accrual balance

E-commerce (Shopify, WooCommerce): Shipping options based on location, cart value, and product type

Government Forms (IRS, USCIS): Eligibility and requirements vary by applicant status and form purpose


Further Reading

Academic Foundations

Practical Implementation

Standards & Specifications

  • Pattern 6: Domain-Aware Validation - Constraints are domain-specific validation
  • Pattern 7: Calculated Dependencies - Context drives calculations
  • Pattern 13: Conditional Requirements - Requirements based on context
  • Volume 2, Pattern 1: Universal Event Log - Tracking constraint violations
  • Volume 2, Pattern 26: Feedback Loop Implementation - Learning from constraint patterns
  • Volume 1, Chapter 3: Document Ontology - A Formal Framework - Modeling constraints formally
  • Volume 1, Chapter 8: Architecture of Domain-Specific Systems - Rule evaluation architecture

Tools & Libraries

Implementation Examples