Guides

Pinning to a schema

A contract and the service that calls it must agree on the names and types of the facts, and on the set of actions. Validation options make that agreement explicit: pass the schema the service implements, and validation refuses any contract that differs from it.

scripts/schema-check.tsexample project
// Fail fast when the contract and the service disagree about facts or actions.
import { readFileSync } from 'node:fs';
import { compileSource, formatDiagnostic, type Primitive } from '../vendor/ulang-lite/src/index.ts';

const source = readFileSync(new URL('../policy/deploy.ul', import.meta.url), 'utf8');

// What the service actually sends and does. Keep this next to the code that builds the facts.
const SERVICE = {
  entity: 'DeployRequest',
  fields: new Map<string, Primitive>([
    ['environment', 'string'], ['tests_passed', 'bool'], ['approvals', 'int'],
    ['author_approved_own_change', 'bool'], ['error_rate_pct', 'float'], ['change_freeze', 'bool'],
  ]),
  actions: new Set(['deploy', 'rollback']),
};

function check(label: string, schema: typeof SERVICE) {
  const result = compileSource(source, schema);
  console.log(`${label}: ${result.ok ? 'ok' : formatDiagnostic(result.diagnostic, 'policy/deploy.ul')}`);
}

check('service schema', SERVICE);
check('service adds a hotfix action', { ...SERVICE, actions: new Set([...SERVICE.actions, 'hotfix']) });
check('service renames approvals', {
  ...SERVICE,
  fields: new Map([...SERVICE.fields].map(([name, type]) => [name === 'approvals' ? 'approval_count' : name, type] as [string, Primitive])),
});
example projectreal output
$ node scripts/schema-check.ts
service schema: ok
service adds a hotfix action: policy/deploy.ul:18:3: semantic[trigger-incomplete]: TRIGGER must list all actions for its contract
service renames approvals: policy/deploy.ul:5:5: semantic[fields]: CONTEXT must exactly match DeployRequest facts schema
OptionFails withWhen
entitysemantic[entity]the contract's entity has another name
fieldssemantic[fields]the entity's fields or their types differ
actionssemantic[trigger-entry], semantic[trigger-incomplete]TRIGGER lists an action the service does not have, or misses one it does

Run the check where the schema lives: in the service's tests, or at startup before serving. µLang Git checks every contract it loads this way.