Specification · v1
API and CLI
ulang-lite is the reference implementation of v1: packages/ulang-lite in the µLang Git repository. It is MIT licensed, ES modules, TypeScript using only erasable syntax, with zero runtime dependencies. It runs unchanged in Cloudflare Workers, Node.js 22.18 and later (native type stripping) and browsers.
1. Getting it
ulang-lite is not on the npm registry. Get it from the repository:
git clone https://github.com/ThinkTechHoldings/ulang-git
cd ulang-git/packages/ulang-lite
node bin/ulang-lite.ts check ../../.forge/rules/ForgeClaim.ul
To use it from your own project, vendor packages/ulang-lite (for example at vendor/ulang-lite) and import src/index.ts on Node 22.18 or later. Node does not strip types from files under node_modules, so if you install it as a package, run npm run build first: that emits plain JavaScript to dist/, which is what the package's main export points at. Bundlers for Workers and browsers can take either.
2. Library
import { compileSource, checkFacts, contractDigest } from './vendor/ulang-lite/src/index.ts';
const result = compileSource(source); // bounds scan → parse → subset → validate → compile
if (!result.ok) {
const d = result.diagnostic; // { kind, code, message, offset, line, column }
throw new Error(`${d.line}:${d.column}: ${d.kind}[${d.code}] ${d.message}`);
}
const contract = result.contracts[0]; // CompiledContract
const valid = result.valid[0]; // ValidContract: AST plus resolved scope
const facts = JSON.parse(body); // { "ClaimRequest": { … } }
const problem = checkFacts(valid, facts); // null, or a message
if (problem) throw new Error(problem);
const decision = contract.decide('grant_claim', facts[contract.entity]);
// { verdict: 'DENY', clause_id: 'ForgeClaim.claim_overlap' }
const digest = contractDigest(valid.contract); // SHA-256 of formatContract(valid.contract)
| Export | Purpose |
|---|---|
compileSource(source, options?) | everything in one call. Returns { ok, contracts, valid } or { ok: false, diagnostic } |
parseSource(source, options?) / parse | bounds scan and parse only, without or with throwing |
validateFile(file, options?) / validateContract | subset, structure and validation |
compileContract(valid) | build the decide function |
checkFacts(valid, facts) | check { "<Entity>": {…} } against the entity |
formatContract / formatFile | canonical text |
canonicalJson(contract) | canonical JSON |
contractDigest(contract) | SHA-256 of the canonical text |
sha256Hex, Sha256 | synchronous FIPS 180-4 SHA-256 |
scanBounds, resolveLimits, DEFAULT_LIMITS, HARD_LIMITS | limits |
formatDiagnostic(d, file?), UlangError | diagnostics |
approxEqual(a, b) | the numeric equality used by == |
3. Validation options
| Option | Default | Effect |
|---|---|---|
limits | defaults | lower any limit for this call (but see errata E2 for condition depth) |
requireEnforced | true | refuse modes other than enforced |
entity | the contract's entity | the entity name the contract must use |
fields | the declared fields | the exact field set, as name → primitive, the entity must declare |
actions | the triggered actions | the exact action set TRIGGER must list |
maxConditionDepth | 16 | condition depth for this call. Keep it between 1 and 256 (errata E2). |
entity, fields and actions pin a contract to an application's schema, which is how the µLang Git forge makes sure a contract governs exactly the actions and facts its API produces.
4. Command line
ulang-lite check FILE... parse and validate; one line per contract, with its SHA-256
ulang-lite eval FILE... --action A --facts F.json decide action A; facts are {"<Entity>": {...}}
ulang-lite format FILE print the canonical text
Exit status: 0 success, 1 a file or the facts were rejected, 2 usage error.
These are real runs of the released command line, in the example project, made when this site was built. The command-line reference has more.
$ ./ulang-lite check policy/deploy.ul
policy/deploy.ul: ok ProductionDeploy (6 clauses; actions deploy, rollback; sha256 686358ddc154b77b5b7b3a24870b8dd6f79b23d376ed039d6824a923284e4059)$ ./ulang-lite eval policy/deploy.ul --action deploy --facts facts/freeze.json
{"verdict":"DENY","clause_id":"ProductionDeploy.change_freeze","contract_sha256":"686358ddc154b77b5b7b3a24870b8dd6f79b23d376ed039d6824a923284e4059"}$ ./ulang-lite check scratch/typo.ul
scratch/typo.ul:29:24: unsupported[unknown-field]: unknown field DeployRequest.error_rate
[exit status 1]5. Conformance
Another implementation conforms to v1 when, for every source and every action and facts, it gives the same:
- accept or reject, and for a rejection the same diagnostic
kind,code,lineandcolumn; - canonical text, and so the same digest;
- decision: verdict and clause id.
The test vectors are a starting set, produced by ulang-lite.