On this page· 5

Records and kinds

  • file format 6
  • Rust SDK 1.0 preview

Plain words: the cover note, cards, links and where-from tags

The body of a pack is JSONL: one JSON object per line, encoded as UTF-8, each line ending in LF. Each object is a record.

The envelope

Two members belong to the format, not to any kind.

Table 1. Envelope members: 2
MemberTypeRequiredMeaning
kstringyesthe kind. A line with no string k is rejected with invalid_record.
vnon-negative integernothe kind's own version. Absent means 1.

The other members belong to the kind.

Typed and opaque records

A record is typed when its kind is registered and its v is no higher than the version the reader supports. In revision 6.0 every registered kind is at version 1. Any other record is opaque.

A typed record is checked against its kind's members. A missing required member or a wrong JSON type is rejected with json. Members the kind does not name are kept and written back after the named ones.

An opaque record is kept whole, not rejected. A writer emits it with the same members, the same values, and the members in the order they were read. That is how a record from a newer writer passes through an older reader.

What a round trip changes

Table 2. Two records, read and written back: 2
ReadWritten
typed{"k":"ent","id":"e2","t":"Person"}{"k":"ent","v":1,"id":"e2","t":"Person","stub":false}
opaque{"k":"x-future","v":7,"id":"n1","z":{"b":1,"a":2}}the same line

A typed record keeps its values and may change its bytes: k comes first, then v, then the kind's members in their declared order. An opaque record keeps its member order; the format promises equal values, not equal bytes, so escapes and number spellings follow the format's own rules.

A 32-bit float member of a typed record, such as salience, is written as the shortest decimal that reads back to the same 32-bit value, not by the binary64 rule of the identity page: 0.123456789 is written 0.12345679. In an opaque record the same member keeps its binary64 spelling. Spec §4.3

The kind registry

Revision 6.0 registers 19 typed kinds, ranked 0 to 18. A kind's rank is its position in the order records are written. Kinds 0 to 7 describe the pack itself, entities and the relations between them, binary data bound to entities, and where statements came from; their members are listed here. ? marks an optional member; types not stated are strings.

Table 3. Graph kinds, ranks 0 to 7: 8
RankKindWhat it isPrimary idMembers
0metawhen the pack was made and from whatcreated_at, |, sourcecreated_at, source, tool?, description?, ngdb_generation? (u64)
1payloada summary of the appendixthe empty stringembedded (bool), csdt_file_checksum? (u32), sections? (array), refs? (array of strings)
2csdt_refa pointer to binary data in another fileref_idref_id, path, shard_set_id? (u32), file_checksum (u32), source_hash, section_type (u16), section_index (u32), record_range?
3entan entity: one thingidid, t, name?, salience? (32-bit float), stub? (bool), properties?, prov?
4rela relation from one entity to anothersrc, tgt, kind, joined by |src, tgt, kind, properties?, prov?
5hypera relation among several entitiesidid, members (array of strings), kind, properties?
6embrefa binding from an entity to a row of binary dataentity_id, |, the target keyentity_id, target, annotations?
7provwhere a fact came fromsubject, method, the time padded to 20 digits, joined by |subject, agent, method, unix_secs (u64), inputs?, notes?

Optional members are left out when absent, with four exceptions a writer always emits: payload.csdt_file_checksum (0), payload.sections ([]), payload.refs ([]) and ent.stub (false).

A stub is an ent record with "stub":true: an entity known by its id only, standing in for a full record kept elsewhere.

Application kinds, ranks 8 to 18

Eleven more kinds are registered for applications built on PLXI. They are typed, like kinds 0 to 7. Section 5.1 of the specification gives each one's name, rank and primary id, which is all that sorting, merging and comparing use. Their member lists are specified separately; the Rust SDK reads and writes all eleven kinds as typed records.

Table 4. Application kinds: 11
RankKindPrimary id
8grounded_answeranswer_id
9citationsource_answer_id, entity_id, span.start padded to 20 digits, joined by |
10clauseclause_id
11knob_setscope, key, unix_secs padded to 20 digits, joined by |
12discoverygid
13promocontent_hash, order padded to 20 digits, joined by |
14codebook_manifestcodebook_id
15sessionsession_id
16sevstep padded to 20 digits, |, t
17session_endend_unix padded to 20 digits
18outcomethe identity form of its outcome member

"Padded to 20 digits" means the number in decimal with zeros in front, 20 characters long, so that primary ids sort as text in number order.

All 19 kinds: Spec §5.1

Reserved kinds

Eleven kind names are reserved. Each has a fixed version-1 shape and no typed reader in revision 6.0, so a reader keeps them opaque.

One is mut, the change record. Change records

The other ten names are specified separately. A reader keeps any record whose kind it does not know as an opaque record, so it needs none of the ten names to read a pack.

Kind names that begin with x- belong to applications: no revision of this specification adds one to the typed kinds (§5.1) or the reserved kinds (§5.2). Spec §5.2

What a line may contain

  • A writer emits no empty lines, no carriage-return byte outside a JSON string, no raw LF inside a value, and no repeated member name inside one object.
  • A reader skips empty lines and strips one trailing carriage return. Skipped lines are not counted as records.
  • A body that is not valid UTF-8 is rejected with invalid_utf8.
Sections