Appearance
Pack authoring
A pack is a versioned, immutable bundle of rules. The live JSON Schema is served at GET /v1/schemas/pack (use it for editor and agent autocomplete).
Anatomy
json
{
"format": 1,
"meta": { "id": "acme.bill.validity", "version": "1.2.0" },
"nodes": [
{
"id": "vat_rate_check",
"kind": "rule",
"expr": "number(facts.vat_rate) == 0.1",
"meta": { "severity": "hard", "citation": "REG-001 s.4", "inputs": ["vat_rate"] }
}
],
"decision": { "aggregation": "weighted_mean", "thresholds": { "pass": "0.9", "warn": "0.7" } }
}meta.idgrammar:^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$;versionis semver.nodes: rule, table, and switch nodes. Each declares itsinputsand aseverity(soft/hard). Hard rules also declare acitation.decision: theaggregationandthresholdsas decimal strings.tests(recommended): embedded vectors that gate publish.
Rules that matter
- All comparison and aggregation is decimal. Numbers in hash-input positions serialize as strings (
"0.9", not0.9). - A hard rule without a citation fails lint.
- Regime-dated rules resolve against
context.transaction_date; a missing date yieldsINCONCLUSIVE, never a silent skip. - A rule/cell/field evaluation error is a hard failure (
INCONCLUSIVEwith a manifest), never a silent non-match.
Author in JSON or YAML
Authoring syntax doesn't affect the published artifact. YAML and JSON of the same logical pack produce a byte-identical content hash, because the hash is over the canonical form, not the bytes you typed. Use whichever you prefer.
Lint locally
opaline-cli lint runs the exact publish gate the server runs (lint, AOT compile, embedded tests) and prints the content hash:
bash
opaline-cli lint ./my-pack.yaml
# pack: acme.bill.validity@1.2.0
# content_hash: 9f86d081…
# lint: OK (publishable)If it says OK (publishable), the document will publish.
Publish & iterate
Publish is immutable and gated (see Getting started). Before moving an alias, use simulate to see what changes:
bash
curl -X POST "https://api.opaline.dev/v1/simulate" \
-H "Authorization: Bearer opl_live_…" \
-H "Content-Type: application/json" \
-d '{
"pack": "acme.bill.validity",
"versions": ["1.2.0", "1.3.0"],
"over": { "inputs": [{ "facts": { "vat_rate": "0.1" }, "context": {} }] }
}'Simulate never meters and never writes the trail. It's the authoring loop for answering "what flips between v1.2 and v1.3?"