Guides
Continuous integration
Two commands catch most contract mistakes before they ship: check, which validates every contract, and the decision tests.
.github/workflows/policy.ymlexample projectname: policy
on: [push, pull_request]
jobs:
contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Validate contracts
run: node vendor/ulang-lite/bin/ulang-lite.ts check policy/*.ul
- name: Decision tests
run: node --test test/*.test.tsThe same two commands, run in the example project when this site was built:
$ node vendor/ulang-lite/bin/ulang-lite.ts check policy/*.ul
policy/deploy.ul: ok ProductionDeploy (6 clauses; actions deploy, rollback; sha256 686358ddc154b77b5b7b3a24870b8dd6f79b23d376ed039d6824a923284e4059)$ node --test test/*.test.ts
TAP version 13
# Subtest: the reviewed contract is the one being tested
ok 1 - the reviewed contract is the one being tested
---
duration_ms …
type: 'test'
...
# Subtest: a green, reviewed deploy is permitted
ok 2 - a green, reviewed deploy is permitted
---
duration_ms …
type: 'test'
...
# Subtest: deny: change freeze in production
ok 3 - deny: change freeze in production
---
duration_ms …
type: 'test'
...
# Subtest: deny: author approved their own change
ok 4 - deny: author approved their own change
---
duration_ms …
type: 'test'
...
# Subtest: deny: error budget exhausted
ok 5 - deny: error budget exhausted
---
duration_ms …
type: 'test'
...
# Subtest: deny: tests failing
ok 6 - deny: tests failing
---
duration_ms …
type: 'test'
...
# Subtest: deny: one approval
ok 7 - deny: one approval
---
duration_ms …
type: 'test'
...
# Subtest: the freeze applies to production only
ok 8 - the freeze applies to production only
---
duration_ms …
type: 'test'
...
# Subtest: rollbacks are not blocked by the freeze
ok 9 - rollbacks are not blocked by the freeze
---
duration_ms …
type: 'test'
...
# Subtest: bad facts are rejected, not decided
ok 10 - bad facts are rejected, not decided
---
duration_ms …
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms …check exits with status 1 if any file is rejected, and prints each problem as file:line:column: kind[code]: message:
$ ./ulang-lite check scratch/typo.ul
scratch/typo.ul:29:24: unsupported[unknown-field]: unknown field DeployRequest.error_rate
[exit status 1]