cbork is a Rust workspace for CDDL and CBOR tooling: a CDDL linting CLI, a CDDL compiler with control-operator semantics,
and the supporting parser and shared-utility crates.
This project does not use Semantic Versioning. See PRAGVER.md for the versioning policy.
The repository is a Cargo workspace organised as:
cbork— command-line tool for CDDL linting, rendering, and validationcbork-cddl-compiler— CDDL compiler and semantic resolvercbork-cddl-parser— CDDL parser and validate entry pointcbork-catalog— compiled catalog of well-known CDDL module namescbork-abnf-parser— ABNF (RFC 5234) parser used by CDDL.abnfannotationscbork-edn— CBOR Extended Diagnostic Notation (EDN) supportcbork-utils— shared CBOR encode/decode helpers
The binary crate is cbork.
To build and install it from this repository:
cargo install --path crates/cborkOr just run the CLI straight from the source tree:
cargo run -p cbork -- --help# Lint a .cddl file
cargo run -p cbork -- lint path/to/schema.cddl
# Show the effective CDDL that the compiler actually reasons about
cargo run -p cbork -- render path/to/schema.cddlThe full usage surface is documented in the cbork crate README.
cbork exposes a global --no-fail switch that forces the process to exit 0 even
when a subcommand would normally report a failure:
# Run lint as an advisory check: diagnostics are still printed,
# but the process exits 0 so the job is not broken.
cbork --no-fail lint --recursive schemas/--no-fail does not suppress diagnostics, change command output, skip work, or downgrade errors —
it only overrides the process exit code.
Use it for:
- advisory/reporting runs in CI where diagnostics should be visible but should not break the job
- staged adoption of lint rules
- local bulk-audit workflows where you want a complete report and a zero shell status
Parse/usage errors that occur before a subcommand runs
(for example, an unknown flag rejected by the argument parser) still produce a non-zero exit.
--no-fail only overrides the command-result exit code.
- CDDL linting —
cbork lintchecks CDDL documents for parse errors, semantic errors, and warnings. Recursive directory scans honor.gitignore(including nested files),.ignore, Git global excludes, and Git exclude files, skip hidden entries by default, and always skip.git/.--hiddenre-includes hidden entries;--no-ignoredisables ignore-file filtering for the scan (hidden entries and.git/are still honored). Explicit file paths are always linted, even when they would be ignored during a recursive scan. - Effective rendering —
cbork renderexpands named rules, generics, sockets, plug choices, and nested control operators into a readable concrete view; the same renderer drives.withinand.anddiagnostics. - Custom control operators — support for the full RFC 8610 / RFC 9165 operator set plus an unofficial CBOR-ecosystem annotation
family (
.x-enc,.x-hash,.x-compressed,.x-brotli/.x-zstd/.x-gzip/.x-deflate). - CBORK compiler directives —
;@ CBORK: Library/;@ CBORK: Export/;@ CBORK: Externextend CDDL with first-party library-level intent declarations. - EDN rendering for raw CBOR —
cbork-edndecodes raw CBOR bytes (single items or concatenated sequences) into an owned EDN-like tree.
cbork-cddl-compiler understands the full RFC 8610 / RFC 9165 control-operator set plus a handful of
unofficial annotations the CBOR community has standardised informally.
Each operator is recognised as a separate transform family so that .within checks compare both the carrier
and the transform identity.
The standard CDDL control operators cover the cases CBOR-native data needs
(.cbor, .size, .bits, numeric bounds, …)
but not the cases the CBOR ecosystem has settled on informally: encrypted blobs (.x-enc), hashes (.x-hash),
and the many flavours of compression (.x-brotli / .x-zstd / .x-gzip / .x-deflate).
cbork lets CDDL authors express those without abandoning .within semantics, by giving each annotation its own transform family.
| Operator | Carrier | Effect |
|---|---|---|
.cbor |
bstr |
Validate the bstr as a well-formed CBOR document. |
.dtrm |
bstr |
Stricter CBOR check (.cbor ⊇ .dtrm — narrower). DRAFT OPERATOR |
.cborseq |
bstr |
Validate as a definite-length CBOR sequence. |
.dtrmseq |
bstr |
Stricter CBOR sequence check. DRAFT OPERATOR |
.prefp |
bstr |
Preferred CBOR serialization check. DRAFT OPERATOR |
.prefpseq |
bstr |
Preferred CBOR sequence serialization check. DRAFT OPERATOR |
.size N |
bstr/tstr |
Length must equal N. |
.bits NAME |
uint |
Bit-layout refinement via a named bit map. |
.gt / .ge / .lt / .le |
numeric | Strict / non-strict numeric bound. |
The serialization-oriented operators (.cbor, .cborseq, .prefp, .prefpseq, .dtrm, and .dtrmseq) accept (an unofficial) any or (official) bstr carriers and a controller that may be a scalar, array, map/group, or wildcard shape.
The any permissive carrier rule is intentional.
It supports schemas such as wrapped = any .dtrm type2, where the source text wants to say "some representation that deterministically serializes as type2" without first narrowing the carrier to bstr.
.x-enc and .x-hash are unofficial wrappers from the CBOR ecosystem that narrow bstr to "the encryption of T"
and "the hash of T" respectively.
They share a single transform family each — .x-enc and .x-hash are not mutually within each other,
and neither is within any compression annotation.
encrypted = bstr .x-enc payload
hashed = bstr .x-hash payload
The LHS rule with a .x-enc controller subtypes the bare bstr carrier on the RHS:
ok = (bstr .x-enc payload-narrow) .within bstr ; always true (carrier narrows)
ok = (bstr .x-enc payload-narrow) .within (bstr .x-enc payload-wide)
Compression annotations are organised into the generic wrapper (.x-compressed) and the per-algorithm wrappers
(.x-brotli, .x-zstd, .x-gzip, .x-deflate).
Each algorithm is within the generic wrapper when its controller subtypes the RHS controller:
brotli-bstr = bstr .x-brotli payload
any-zstd = bstr .x-zstd payload-wide
ok = brotli-bstr .within (bstr .x-compressed payload-wide)
Two different algorithms are NOT mutually within each other
(bstr .x-brotli T is not within bstr .x-zstd T), and the generic wrapper is NOT within any specific algorithm.
The .abnf / .abnfb annotated forms (.x-compressed.abnf, .x-brotli.abnfb, …) collapse to the same transform family
for .within subtype purposes while still preserving enough detail for literal/ABNF validation.
This matches the unofficial CBOR convention.
The full compatibility matrix that drives .within subtype checks is summarised below.
Rows are the LHS operator, columns are the RHS operator:
bstr (carrier) |
.x-enc |
.x-hash |
.x-compressed |
.x-brotli/.x-zstd/.x-gzip/.x-deflate |
|
|---|---|---|---|---|---|
.x-enc |
✓ | ✓ (controllers) | ✗ | ✗ | ✗ |
.x-hash |
✓ | ✗ | ✓ (controllers) | ✗ | ✗ |
.x-compressed |
✓ | ✗ | ✗ | ✓ (controllers) | ✗ |
.x-brotli etc. |
✓ | ✗ | ✗ | ✓ (controllers) | ✓ same algorithm, ✗ different algorithms |
✓ means the LHS subtypes the RHS.
✗ means the LHS is not within the RHS and .within emits a control-mismatch diagnostic (E030)
that names the incompatible operators.
is structurally well-formed.
The any on the controller side is the common way to write "a CBOR document holding anything" without forcing the CDDL author to
commit to a specific inner schema at the use site.
A ;!-prefixed comment line is a markdown-formatted documentation comment.
The body of the comment is interpreted as CommonMark.
A contiguous run of ;! lines forms a documentation block; blank lines or CDDL definitions break that contiguity.
Comment blocks that appear at the top of a CDDL file, or disconnected from any CDDL definition are general document comments.
A comment block is bound to the next CDDL definition in the file if it is directly attached
(skipping any plain ;, ;@, and ;# comments in between), which becomes the "documented definition" for that block.
This is what the user sees in the optional documentation lint pass
(cbork lint --doc):
the tool synthesizes the documentation blocks into a Markdown document
(with the definitions preserved at their annotated positions),
runs rumdl against the result, and translates the lint warnings back to CDDL-anchored diagnostics.
A module-level (file-level) documentation block must open with a level-1 heading; exported
(;@ CBORK: Export) definitions must each carry their own definition-level block.
Example:
;! # Person types
;!
;! CDDL file which defines people data objects.
; ^^ This is a file level comment and does not attach to any definition.
; These NORMAL CDDL comments are not part of "documentation" and are internal comments only.
;! ## Payload
;!
;! These are **person** type payloads.
; ^^ Defining a section of the this CDDL file.
; vv This must be directly attached to `person` or it is not considered a comment for that definition.
;! ### `person` payload
;!
;! Carries a single person record with a canonical `name`, an `age` in
;! years, and an optional contact `email`.
; This comment is not documentation, but does not break linkage of the comment block to the definition
person = {
name: tstr, ; This is not a documentation comment, its ignored for documentation purposes.
age: uint, ;! Documentation comments can not attach to the RHS of any definition,
? email: tstr, ;! so these will emit a warning and be ignored for documentation purposes.
}
cbork-cddl-compiler implements the module-system syntax from the
CDDL Modules draft: ;# include … and ;# import …,
both supporting the from … and as … clause options.
The target filename is classified by how it is written:
- Unquoted base names (e.g.
rfc9052) are default standard CDDL documents and resolve through the compile-time catalog (cddl/rfc-std/). Two imports of the same base name produce rules with the same source origin (catalog:<name>), so re-imports compose cleanly. - Quoted relative paths (e.g.
"./somedir/file.cddl"or"../common.cddl") are resolved relative to the CDDL file in which the directive appears, and the file content is read from the local filesystem. - Quoted absolute paths (anything starting with
/, e.g."/repo/root/file.cddl") are resolved against the logicalroot_pathpassed to the compiler, which anchors absolute includes to a known filesystem root so the build is reproducible.
Every form is read from the local filesystem
(or from the in-tree catalog for base names); cbork never downloads or fetches from a network.
An unresolved file produces E009; a failed read produces E011.
Example:
;# include "./common/common-types.cddl"
;# import cose_sign from "rfc9052" as sign
;# import rfc8610
CBORK uses ;@-prefixed comment annotations to express library-level intents that the CDDL grammar cannot encode.
All CBORK directives live in the CBORK: namespace;
directives from any other namespace are surfaced as warnings so users notice that their tool annotation was ignored.
| Directive | Effect |
|---|---|
;@ CBORK: Library |
Marks this file as a reusable library module. Must appear before any non-comment content. |
;@ CBORK: Export |
Marks the next rule as part of the library's public export surface. The compiler tags the rule with MetaData::Exported and records the name in CompiledCDDL::exported_names. |
;@ CBORK: Extern <name>,... |
Declares names that this library treats as external. Requires ;@ CBORK: Library in the same file. |
Example:
;@ CBORK: Library
;@ CBORK: Export
public-rule = uint
private-helper = bstr .size 16 ; not exported, internal use only
A ;@ CBORK: Export must be followed (after any whitespace or doc comments) by a single rule.
The following situations are all rejected with E022:
;@ CBORK: Exportin a non-library file.;@ CBORK: Exportwith no following rule (EOF).;@ CBORK: Exportimmediately before animport/includedirective comment.- Two consecutive
;@ CBORK: Exportdirectives with no rule between them.
Unknown CBORK directives (e.g. ;@ CBORK: Thing) emit an E021 diagnostic
because they look like active CBORK processing directives but are not recognized.
Directives from any other namespace (e.g. ;@ OTHER: ...) emit a W002 warning so users notice
that their tool annotation was ignored:
;@ OTHER: do-something ; ignored; emits W002
The recognized CBORK directive set is small and stable — see the table above.
Adding a new directive is a small change in crates/cbork-cddl-compiler/src/compiled.rs
(the parse_cbork_comment function and the CborkDirective enum).
When a file directly imports or includes another file that is marked as a CBORK library,
any reference from the consumer's own rules to a symbol defined in that library must point at an exported
(;@ CBORK: Export) or externally-declared (;@ CBORK: Extern) name.
Direct references to non-exported helpers emit a W003 warning; strict mode fails on that warning.
The contract does not apply when:
- the consumer references the imported name transitively through an exported symbol (the helper is private but reachable through the library's own exported surface);
- the imported module is not marked as a CBORK library (unlabelled imports and unlabelled includes never participate);
- the consumer itself has declared the symbol as
;@ CBORK: Extern <name>(the consumer has opted in to that name explicitly); - the reference resolves to a postlude-injected primitive
(e.g.
uint,bstr,any) rather than a definition from the imported file.
In addition to the direct-use contract, the compiler emits warnings when a consumer imports or includes symbols that it never references:
| Code | Trigger |
|---|---|
W004 |
The whole import / include directive contributes no referenced symbol. For from directives this fires when every name in the from clause is unused. |
W005 |
A specific selected name on a from clause is never referenced. Fires per name, even when the directive as a whole is partially used. |
The unused checks walk the consumer's own rules in the post-prune tree, so references that the reachability pruner dropped (or references inside the imported rules themselves) do not falsely satisfy the "used" check.
Library exports are public API surface, not a required-use contract. A whole-library import is valid when the consumer references any imported symbol it needs; unused sibling exports from that library do not produce a consumer-side warning.
crates/ first-party Rust workspace (cbork, cbork-cddl-*, cbork-*, cbork-utils)
cddl/ bundled CDDL support data: standard-library CDDL (cddl/rfc-std/) and test vectors (cddl/vectors/)
rfc/ literal copies of bundled RFC / Internet-Draft text (IETF Trust terms)
extension/zed/ Zed editor extension for CDDL (grammar is pulled at install time, not vendored — see extension/zed/README.md)
plans/ release / restructure plans tracking in-flight work
To work on this repository locally you need:
just— the task runner that drives everything below- Podman — the container runtime used by the shared validation flow
- the
sakura-dev-toolsbuild image, which providesmoon,rustup/cargo,cargo deny,cargo-nextest,rumdl, andcspell(thejustrecipes below build it on demand)
The shared validation flow is containerized:
just fix-ciThis builds the shared tools image if needed, then runs moon run :fix && moon run :ci inside the container.
For a single side of that flow, use just fix or just ci.
To bypass the container and run moon directly on the host, use just local fix / just local ci.
The cbork Rust crate, the cbork-cddl-* Rust crates,
and the supporting documentation under rfc/ and cddl/ are worked on through the same container flow.
The tree-sitter grammar under extension/zed/grammars/cddl/ is a separate subproject
(its own container toolchain; see its AGENTS.md).
just fix-ci runs:
cargo fmt --checkandcargo clippy -- -D warnings- license checks (
cargo deny check licenses bansandcargo deny check advisories) - the cbork crate's release build and the full
cargo nextestsuite - markdown lint (
rumdl check) and spell-check (cspell) across the documentation - a release build of the
cborkbinary and a strict lint pass overcddl/rfc-std/
This workspace is a mixed-license project.
Each first-party crate is licensed under the expression declared in its Cargo.toml,
which is the authoritative source for that crate's license.
The LICENSE at the repository root summarizes the structure and states the default license for directories
that do not carry their own LICENSE file.
| Crate | License | Local license file(s) | Full text |
|---|---|---|---|
cbork |
AGPL-3.0-only |
crates/cbork/LICENSE |
root LICENSE-AGPL-3.0 |
cbork-cddl-compiler |
MPL-2.0 |
crates/cbork-cddl-compiler/LICENSE |
root LICENSE-MPL-2.0 |
cbork-catalog |
MPL-2.0 |
crates/cbork-catalog/LICENSE |
root LICENSE-MPL-2.0 |
cbork-edn |
MPL-2.0 |
crates/cbork-edn/LICENSE |
root LICENSE-MPL-2.0 |
cbork-cddl-parser |
MIT OR Apache-2.0 |
crates/cbork-cddl-parser/LICENSE-MIT, LICENSE-APACHE |
each file |
cbork-abnf-parser |
MIT OR Apache-2.0 |
crates/cbork-abnf-parser/LICENSE-MIT, LICENSE-APACHE |
each file |
cbork-utils |
MIT OR Apache-2.0 |
crates/cbork-utils/LICENSE-MIT, LICENSE-APACHE |
each file |
Bundled RFC and Internet-Draft text under rfc/
and rfc/related/ is redistributed verbatim under the IETF Trust License: Terms for Reproducing RFCs and Drafts;
see rfc/README.md.
The CDDL standard-library files under cddl/rfc-std/ are from cabo/cddlc data
and are covered by the MIT LICENSE in that directory.
Issues are intentionally disabled.
See CONTRIBUTING for details if you wish to contribute to this project.
