Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Records and manifest

Records and manifest

What is committed, and why

On this page

You commit a file only if it can't be worked out again from today's source, or if it is a promise whose diff a person must read. Migrations, renames and the lock hold history that today's source no longer has. The API record is a promise to your clients. Acceptances are people's decisions. Anything else would go stale, conflict in merges and tempt people to edit it by hand, so it is a build output instead.

Record files

Every record but the lock is a .jadpo_record file, kept next to what it describes:

  • api/main.jadpo_record, the API record;
  • database/identities.jadpo_record, the identity registry;
  • each translations/sources.jadpo_record;
  • each installed package's surface.jadpo_record;
  • the root's ignored_suggestions.jadpo_record, the suggestions a team keeps quiet (Suggestions), and taught_suggestions.jadpo_record, the patterns it taught jadpo suggest.

Each is in Jadpo's readable format, not JSON. Its first line says what generated it and which command updates it, as the API record's does:

text
// Generated by Jadpo from the routes of API main; update it with `jadpo api update`, never by hand.record api 1

Every record but the migrations is written in the record format: the header line, then record <kind> <format>, then its entries. jadpo.lock keeps its name. Files people write, such as acceptances, are not records. A migration's folder keeps its own files (Migrations).

How jadpo check keeps records up to date

You never edit a record by hand. jadpo check works out each record again and fails while one disagrees with the source, naming the command that brings it up to date. A hand edit is an error naming that command. There are three such errors:

  • record.out_of_date: the record disagrees with the source.
  • record.edited: Jadpo didn't write the record that way.
  • record.format_newer: a newer Jadpo wrote it.

The API record is api/<api>.jadpo_record, a view rebuilt from the routes, with no version: line. The OpenAPI document's version is the first 12 hex digits of the SHA-256 of the record the routes give today, which is the committed record whenever jadpo check passes. The identity registry, the translation sources, the suggestion records and jadpo.lock are ledgers of one-line entries (Checking a record explains views and ledgers).

jadpo records

jadpo records lists every record, what writes it and whether it is up to date, and names the command for any record that isn't. It groups them by the command that writes them:

text
$ jadpo recordsjadpo api update✗ api/main.jadpo_record             out of datejadpo rename, jadpo domain or jadpo fmt --layout✓ database/identities.jadpo_record  up to date✗ records out of date  1 out of date · 2 records→ jadpo api update  brings api/main.jadpo_record up to date

jadpo records update brings every record up to date at once. It is also how a project moves a record from an older name (api/<api>.api, database/identities.json) to its name now (Checking a record).

Migrations

Migrations are records too. jadpo migrate new writes each folder in database/migrations/: snapshot.json, postgres.sql and, when the migration needs a person's decision, changes.jadpo. jadpo check fails, naming the command to run, while the entities and the latest snapshot disagree, while the latest migration waits for a decision, and when a record can't be read. Each folder is a migration_snapshot node in the manifest.

Folders at the root

The root of a project holds api/, acceptances/, packages/ and jadpo.lock beside the rest. packages/ holds the installed packages. They are committed, so every upgrade is an ordinary diff. Each is checked against its digest in the lock and is read-only, but only its surface.jadpo_record is a record. Your app's own integrations, in shared/providers/, are source, written like any other code (Integrations).

Checking a record

A record is one of two shapes:

  • A view, such as the API record or a package surface, is rebuilt from the source and compared with the committed file byte for byte.
  • A ledger, such as the identity registry, the translation sources, the suggestion records or the lock, is a list of one-line entries that hold history.

A hand edit of a view is told by its shape. Without the base commit's build, a view that differs from what its writer gives today is record.edited when Jadpo couldn't have written it, and record.out_of_date otherwise. Jadpo couldn't have written a comment, a fact outside the kind's closed list, a tab, a trailing space, a layout Jadpo doesn't write, or a header other than the one it writes. Both errors name the same command, which writes the record again, so a careful edit of one fact reads as out of date.

A ledger is checked against its own rewrite. Each line must read, each key must appear once, and the file must equal what Jadpo writes from its lines: sorted, with the header in the newest format.

  • Lines that git's union merge left out of order are record.edited, and jadpo records update puts them right.
  • Two lines for one key, such as one thing renamed two ways or one package locked twice, are record.edited naming both lines, and are left for a person to settle.

Checking that each line added since the base is one its command could write needs the base commit's build too.

jadpo records update brings every record up to date at once. It writes each view from the source, rewrites each ledger, and moves a record under its old name. It is the command an error's next: line names for a ledger, because a ledger's own commands (jadpo rename, jadpo translations update, jadpo suggest ignore) need arguments.

Old names are moved, not read forever. api/<api>.api and database/identities.json are record.out_of_date ("the record's old name"). jadpo api update and jadpo records update write the new file and remove the old one. Until then the old registry's lines are still read, so no key changes before the move.

Older formats are read and rewritten. ignored_suggestions and taught_suggestions are at format 2, with the shared header. Their format 1, with its own header line, is still read, and jadpo records update rewrites it.

Records don't stop a program running. record.out_of_date and record.edited are promises that jadpo check and CI hold you to, so jadpo test and jadpo dev run past them. record.format_newer stops them, because this Jadpo can't read what the project holds.

jadpo records shows each record's state as a symbol and a word under the command that writes it: ✓ up to date, ✗ out of date, ✗ edited by hand, ✗ missing, ✗ old name, ✗ newer format, or – not checked while the source has errors. Like jadpo check, it fails while any record needs its command.

What some entries hold:

  • The API record. An api <name> entry carries one authentication <method> line per method, sorted, and idempotency required. The entry is left out when it has neither. A route's generated and caller facts come before input. Enum variants are sorted like fields, as variant <name>, and a variant's fields sit one indent deeper. Types sort by born name without their domain, then by name now. A failure's entry is its status, then its message as a Jadpo string, then its client fields.
  • The lock. A git source records the repository, the tag and the tag's commit: package AcmeCouriers.v1 version 1.4.0 git github.com/acme/acme-couriers tag v1.4.0 commit <40 hex> digest sha256:<64 hex>. A registry source is version <v> registry digest <d>, and a path source is version <v> path <folder> digest <d>. A link is a live folder, so it has no version or digest: package AcmePricing.v2 link ../acme-pricing. Versions are written like 1.4.0, with an optional pre-release. A commit is 40 or 64 lower-case hex digits. Lines sort by name, then by major version as a number.
  • A project that pins the Jadpo release it runs with has that pin as the lock's first line, jadpo version 0.1.2. The release has no pre-release, and it must be on the release line app.jadpo names, such as 0.1. A pin on another release line is record.out_of_date (Which jadpo runs).

The record format

Every record is written in one small line format, an outline in Jadpo's own words. A change touches only the lines of the fact that changed, and git's hunk header names the entry the change is in.

The header. Line 1 is // Generated by Jadpo from <what it describes>; update it with <command>, never by hand. Line 2 is record <kind> <format>, where the kind is one of api, events, surface, identities, translations, ignored_suggestions, taught_suggestions and lock. Any other // line is a hand edit. Nothing anywhere in a record changes on its own: it holds no timestamp, compiler version, count, total, digest of the whole file or alignment.

Entries. A heading starts in column 0, with a word from the kind's closed list (POST /todos, operation bookings.create). Its members follow, indented four spaces, and a member's own fields, such as an enum variant's, are indented eight. Nothing goes deeper. A member is one of three things:

  • a description, /// text;
  • a field, name: value, written as in source;
  • a fact, which starts with a lower-case word from the kind's closed list: status 201, fails todo_not_found, file types/booking.jadpo.

A list repeats its word, one item per line. A flag is a fact that is present or absent (changes, idempotent, accepted), never changes: true. A line's kind is known from its first characters, so a tool can read a record without a Jadpo parser.

Views and ledgers. A view's entries are outlines. A ledger's entry is its heading alone, one self-contained line, so git's union merge can combine what two branches added. jadpo check then catches what that merge gets wrong (Checking a record).

Values are Jadpo, written on one line as the formatter writes them in source: names, dotted names, strings, numbers and type expressions (Text { min_length: 1, max_length: 200 }). Paths and URLs are never quoted, and a file path is relative to the record's folder. A digest is sha256: and 64 lower-case hex digits. A hash is 12 hex digits, since it isn't a security check.

Layout. UTF-8, LF line endings, a final newline, no trailing spaces, no tabs, one space between tokens and no alignment, so a long new name never re-indents its neighbours. One blank line follows the header and separates entries that have members; one-line entries have no blank lines between them. Strings use Jadpo's escapes and only those: \", \\, \n, \r, \t. A description is the declaration's first paragraph joined onto one /// line, so rewrapping the comment in source changes nothing. Records are compared with CRLF read as LF.

Order is by identity. Everything is sorted; only a function's parameters keep the order they are written in. Fields sort by name, since field order is no part of a JSON contract. Repeated facts (fails, host) sort by value. Where the identity registry has a line for a thing, the thing sorts by its born name, so a rename changes lines in place instead of moving a block.

Record Sections, in order Within a section
api api, routes, named types, failures Routes by path, then method; types by born name; failures by code
surface package, provider, provides, operations, actions, polls, events, functions, messages, types, failures, recipes By born name; operations by resource.operation
identities One By born name, then kind
translations One By locale, then member
lock One The jadpo pin, then by package name, then major version

Versions. Each kind has its own format number, starting at 1. Any change to what a kind holds or how it is laid out raises it, additions included, since a view is compared byte for byte. A view in an older format is out of date, and its command rewrites it. A ledger holds history, so Jadpo reads it in every format it has ever written for that kind, and rewrites it in the newest. A record in a newer format than this Jadpo writes is record.format_newer: Jadpo never guesses. The lock's format 1 already holds the jadpo version pin, since no lock had shipped before the pin was added.

The identity registry's lines are the kind, the born name, -> and the name now. A field, variant or rule follows what it belongs to, so its line gives only its own new name, and renaming Task again never touches it:

text
entity Todo -> Taskfield Todo.priority -> importance

A translation source's line holds three facts: the hash of the default wording it was translated from (from), the hash of its own wording when that was recorded (wording), and accepted once a person accepted it (Translations as messages). Whether a translation is stale or changed is worked out from those, never stored:

text
translation de_de.order_shipped from 3f9a1c0e2b7d wording 77b2e1d4c8a9 acceptedtranslation fr_fr.order_shipped from 3f9a1c0e2b7d wording a01c9e44f3b8

Records in git and in your editor

jadpo new writes these lines into a new project's .gitattributes. linguist-generated=false keeps every record's diff expanded in a pull request, since records are committed so that their diff is read. merge=union is git's own merge driver for ledgers, and needs no set-up:

text
*.jadpo_record                       text eol=lf linguist-generated=falsejadpo.lock                           text eol=lf linguist-generated=false merge=uniondatabase/identities.jadpo_record     merge=union**/translations/sources.jadpo_record merge=union

Editors get a record language: a TextMate grammar, source.jadpo-record, in editors/vscode, and jadpo lsp serving a record's errors, outline, file links and folding.

Not built yet: the base commit's build, which comes with jadpo approval, so every view is checked by its shape alone, and the record language for editors (What is built).

Markdown for agents: /docs/what-is-committed-and-why.md.Built from Jadpo main at a707efb.