OC-201b · Module 1

Module Interface Contracts

3 min read

Every module makes two promises: what it accepts as input and what it returns as output. These promises are the module contract. When you document the contract before writing the implementation, something changes about how you think about the module. You stop thinking about "what does this code do" and start thinking about "what does this module guarantee." That shift matters because modules that guarantee specific interfaces can be composed, replaced, and tested independently.

A contract has four components. The input schema — what fields are required, what types they are, what happens when optional fields are missing. The output schema — what the module returns on success, always in the same structure. The error contract — what the module returns on failure, with enough detail for the caller to decide what to do next. And the side effects declaration — what the module changes in the outside world: database writes, API calls, file mutations. Modules with undocumented side effects are tents. They look functional until the wind blows.

// Module contract: Weather Briefing
interface WeatherInput {
  city: string;          // Required: city name or zip code
  units?: 'metric' | 'imperial';  // Optional: defaults to metric
}

interface WeatherOutput {
  city: string;
  temperature: number;
  condition: string;
  forecast: { day: string; high: number; low: number }[];
  source: string;        // Always present: API attribution
}

interface WeatherError {
  code: 'CITY_NOT_FOUND' | 'API_TIMEOUT' | 'RATE_LIMITED';
  message: string;
  retryable: boolean;    // Caller knows whether to retry
}

Do This

  • Define input and output schemas with explicit types before writing any logic
  • Include an error contract that tells callers whether to retry or abort
  • Declare all side effects — database writes, API calls, file mutations
  • Return null with a reason for missing optional data, never omit the key

Avoid This

  • Let the implementation define the contract by accident
  • Return different shapes from the same module depending on what happened
  • Throw unstructured exceptions that the caller cannot programmatically handle
  • Hide side effects inside helper functions where callers cannot see them