Graven

Tools

gravenc is the schema compiler and also the tool for working with Graven data by hand: looking inside files, converting to and from JSON, checking logs after a crash and reviewing schema changes. Every data tool reads the .graven schema and its lockfile, so it decodes exactly what generated code would.

Command What it is for
gravenc check Validate schemas and their compatibility with the lockfile (CI).
gravenc update Apply the compatibility rules and write the lockfile.
gravenc gen Generate Rust code.
gravenc diff List what changed since the lockfile, with a verdict per change.
gravenc inspect Read the messages in a file.
gravenc to-json Decode messages as JSON.
gravenc from-json Encode JSON as messages.
gravenc scan Check a file of envelopes: intact records, a cut-off tail, corruption.

Install it with cargo install graven-compiler, or from a clone with cargo install --path crates/graven-compiler.

Exit codes are the same for every command: 0 success, 1 an error or a problem found in the data, 2 incorrect usage.

check

gravenc check [--locked] <schema>...

Parses and checks each schema and, when a lockfile exists, the compatibility of the schema with it. Schema ids are compared across all the schemas given: two differently named tables with the same id would make their envelopes indistinguishable, so that is an error. Nothing is written. Schemas that the given ones import are found under their directories and checked with them. With --locked, a missing or out-of-date lockfile is an error: this is the command for continuous integration.

gravenc check --locked schemas/*.graven

update

gravenc update <schema>... [--init | --from <lock>]

The only way a lockfile changes. update compares the schema with the lockfile using the compatibility rules and rewrites the lockfile only when there is no error and no unconfirmed warning.

gen

gravenc gen <schema> -o <dir>

Writes <dir>/<schema name>.rs, the same code the build.rs API produces, plus the code of the schemas it imports. Most projects use build.rs instead; see getting started.

diff

gravenc diff <schema> [--against <lock>]

A readable report of every change between the lockfile (or another lockfile given with --against) and the schema, each with the verdict update would give. Use it to review a schema change in a pull request.

$ gravenc diff bank.graven
bank.graven compared with bank.graven.lock

table Entry
  ~ amount @1: type i64 → i32      error: type-changed

table Transaction
  ~ note @6: optional → reserved   allowed: reserved-optional
  + fee @7: i64 = 0                allowed: new-default
  + channel @8: string?            allowed: new-optional

enum Currency
  + ARS = 3                        allowed: new-enum-variant

5 change(s): 4 allowed, 1 blocking

Fields that only moved because an earlier field changed size are not listed separately: the change that caused it is.

inspect

gravenc inspect <schema> <file> [--root <Table>] [--raw]

Decodes every message in a file and prints it as indented JSON, with the offset and the table of each one. A file that starts with an envelope is read as a sequence of envelopes, and each message's table is found from its schema id. A bare message has no schema id, so it needs --root. If the schema declares validation rules, inspect checks the built-in rules (range, len, finite, known) and reports each check(name) as not checked.

$ gravenc inspect bank.graven log.bin
#0  offset 0  bank.Transaction  129 bytes
{
  "id": 42,
  "ts": 1700000000,
  "currency": "USD",
  "from_shard": 3,
  "to_shard": 7,
  "entries": [
    {
      "account": 1001,
      "amount": -2500,
      "memo": "transfer"
    },
    {
      "account": 2002,
      "amount": 2500
    }
  ],
  "note": "rent"
}
#1  offset 129  bank.Transaction  67 bytes
…

Every message is fully verified before it is shown: a damaged message is reported with its error instead of being half-printed.

to-json and from-json

gravenc to-json   <schema> <file> [--root <Table>] [--raw] [--pretty]
gravenc from-json <schema> --root <Table> <input.json | -> -o <output | -> [--raw]

to-json prints one JSON document per message (JSON Lines), ready for jq, scripts or other systems. from-json reads one or more JSON documents and writes them as a file of envelopes, or as a single bare message with --raw. Decoding the output of from-json with to-json gives the same documents back.

gravenc to-json bank.graven log.bin | jq 'select(.currency == "USD") | .id'
gravenc from-json bank.graven --root Transaction fixtures.jsonl -o fixtures.bin

The JSON follows the schema:

Schema type JSON
integers numbers; u128/i128 values outside the 64-bit range are decimal strings
f32, f64 numbers; "NaN", "inf" and "-inf" for non-finite values
bool true / false
enum the variant name; a number for a value the schema does not know
string string
bytes lowercase hex string
struct, table object, fields in declaration order
list array
union {"Variant": { … }}; {"$tag": 5} for a tag the schema does not know

Absent optional fields are left out and absent fields with a default show the default. On input, null is the same as leaving a field out. from-json rejects documents that do not match the schema and names the place: document 0: Order.total: struct Money needs every field; currency is missing.

scan

gravenc scan <file> [--schema <schema>] [--truncate]

Checks a file of envelopes, such as an append-only log, from start to end. It reports how many records are intact and what stopped the scan, if anything. With --schema, every payload is also fully verified and records are counted per table.

A crash during a write leaves an incomplete record at the end of the file:

$ gravenc scan orders.bin --schema bank.graven
orders.bin: 1 record(s) intact, 129 bytes
  bank.Transaction                 1
incomplete tail at offset 129: 57 bytes of a record that was cut short, which is what a crash during a write leaves behind.
Every record before it is intact. Run with --truncate to cut the file at offset 129.

--truncate removes that tail, so the file ends after the last intact record and new records can be appended again. It never removes anything else.

Damaged data inside the file is a different situation, and scan says so:

$ gravenc scan damaged.bin
damaged.bin: 0 record(s) intact, 0 bytes
corrupt data at offset 0: ChecksumMismatch: the record is complete but its CRC does not match (corruption).
The 0 record(s) before it are intact.
The next intact record starts at offset 129; 1 intact record(s) follow from there (129 bytes in between cannot be read).
--truncate only removes an incomplete tail, never corrupt data.

The distinction comes from the envelope's normative check order (spec §8.4): an incomplete record and a corrupt one can never be confused.

Editor support

The repository includes a Visual Studio Code extension in editors/vscode with syntax highlighting for .graven schemas and .graven.lock files, comment toggling and bracket matching. To install it:

cd editors/vscode
npx @vscode/vsce package
code --install-extension graven-0.1.0.vsix