Contents

Lace-010 · Records

Tags: record, stored-format

Purpose

Distributed data needs identity that follows bytes plus enough generic structure for indexing and policy after bytes arrive from another source. Lace records provide canonical bytes with stable hashes, application-defined field occurrences, and optional By/Mark evidence.

This document enables record and higher-layer implementors to construct, parse, validate, store, hash, mark, and index the current H3 Blob, Plex, and Seal forms. Applications, not the record protocol, define field schemas and field-value semantics.

Defines

Blob, Plex, and Seal bytes; record glyphs and hashes; record streams; generic field lines; field ordering and limits; record-link fields; constructor form selection; and record-size boundaries.

Every stored record starts with a record glyph line

🖧: <hash-text>

For H3, <hash-text> is T.<b64a>.H3, where T is B, P, or S and the B64A payload is the BLAKE3-256 digest of every canonical byte after the record glyph line LF. The digest payload is exactly 32 bytes and therefore 43 canonical 006 B64A characters.

Implementations MUST verify that the glyph type matches the parsed record form and that the digest matches the canonical payload. A complete stored record contains exactly one record and no bytes after the bytes consumed by its structure and Blob Data-Length.

By convention, a file whose content is exactly one complete stored record ends its name with .lr (Lace record), after any name its data would take: a Seal whose data is notes.json is stored as notes.json.lr. The suffix marks canonical record bytes, not their data; readers still parse and validate them.

Record streams concatenate complete records

A record stream is zero or more complete stored records concatenated in order, with no header, separator, or trailer. Each record’s own structure and Blob Data-Length delimit it, and the next record starts at the following byte. A reader MUST validate every record as this document defines and MUST reject a stream that ends inside a record or continues with bytes that do not form a valid record. An empty stream holds no records.

By convention, a file whose content is a record stream ends its name with .lrs (Lace record stream). A .lr file is a one-record stream.

Shared field-line rules keep bytes canonical

A field line has this exact syntax:

<Name>: <value>\n

Implementations MUST enforce these rules:

The structural Data-Length, By, and Mark lines satisfy the same line and text rules in addition to their form-specific rules below.

Blob records carry opaque bytes

A Blob gives arbitrary bytes hash identity without application fields or Mark evidence:

🖧: B.<digest>.H3
Data-Length: <len>

<data>

The Blob canonical payload is the Data-Length line, one blank LF line, and exactly <len> data bytes.

Blob rules:

Blob data is opaque and may contain any bytes, including bytes that resemble field lines, record glyphs, or ILTP items.

Plex records attach generic fields to a Blob

A Plex contains one or more application field occurrences followed by one complete embedded Blob:

🖧: P.<digest>.H3
<Field-1>: <value-1>
...
<Field-N>: <value-N>
🖧: B.<digest>.H3
Data-Length: <len>

<data>

The Plex canonical payload is all application field lines followed immediately by the full embedded Blob record. N MUST be in 1..512.

All application field names use the same occurrence model. Applications define their schemas, including required fields, singleton constraints, identifiers, scopes, value shapes, version values, ordering meaning, and all other interpretation.

By convention, prefer #<application>: <kind> (for example, #Dots: game) over generic application labels such as Kind: dots, distinguishing application-kind fields from ordinary properties and sorting them before alphabetic field names.

Plex application fields are canonical in this order:

  1. occurrences sort by field name using bytewise ascending UTF-8 order; and
  2. occurrences with the same name are consecutive and retain caller order.

A constructor MUST preserve supplied order among same-name occurrences while sorting different names. A parser MUST reject a decrease in adjacent field-name bytes. Values do not participate in canonical field ordering.

The names reserved from application use are exactly:

Data-Length
By
Mark
🖧
⋯🖧

Data-Length, By, Mark, and 🖧 are current structural names; ⋯🖧 is reserved for record structure and has no application-field role. A Plex application field using any reserved name is invalid. Names starting with : are 020 synthetic field names; they never occur in record bytes, since no field name contains :.

Seal records carry one Mark over one Plex

A Seal contains exactly the structural By and Mark lines followed by one complete embedded Plex:

🖧: S.<digest>.H3
By: <verification-key-text>
Mark: <mark>
🖧: P.<digest>.H3
<Field-1>: <value-1>
...
<Field-N>: <value-N>
🖧: B.<digest>.H3
Data-Length: <len>

<data>

The Seal canonical payload is the By line, the Mark line, and the full embedded Plex record.

Seal rules:

A Seal proves only that the matching By-secret value produced this Mark over the digest of these exact Plex bytes. It does not by itself prove human identity, authorship, truth, authorization, ownership, observation, permission, or policy selection.

Generic constructors select one record form

An application-facing constructor accepts data bytes, an ordered sequence of application field occurrences, and optionally a By-secret value. It selects the stored form exactly as follows:

empty specification               -> empty Blob
only data                          -> Blob
one or more fields                 -> Plex
By-secret with no fields           -> error
By-secret plus one or more fields  -> Seal over Plex

Omitted data means empty bytes. Constructors MUST NOT manufacture an application field, placeholder, identifier, scope, time, or version. They validate field names and values, reject reserved names, preserve same-name caller order, sort field names canonically, and derive By and the Mark from the supplied By-secret value under 009. A public By value is record output, not a substitute for the By-secret constructor input.

A Plex application field named +Link or ending with +Link is a record-link field. It remains an ordinary sorted, hash-significant field occurrence subject to the same field count and line limits.

A well-formed record-link value is:

<link-data> <target-hash>

Rules:

Readers MUST split at the last ASCII space without trimming either part. That one separator byte is not link data; any preceding spaces are. For example, name B.<digest>.H3 carries link data name, including its trailing space.

The complete name, : separator, link data, separator space, and target hash share the ordinary 128-byte field-line budget. A malformed record-link value does not invalidate an otherwise valid field occurrence; it remains an ordinary application field. Layers that index record links MUST ignore malformed values. An unsupported target format may be indexed as a target. It produces validated local record facts only after its bytes validate under a locally supported record definition.

Examples:

+Link: evidence S.EXAMPLE_SEAL_HASH.H3
Chunk+Link: 0..33554432 B.EXAMPLE_BLOB_HASH.H3

The following conventions are opt-in higher-layer interpretations of ordinary links. They reserve no names, change no generic constructor or record-validation rule, and add no fact families. A convention-invalid record remains a valid H3 record when it satisfies the generic rules above; its Field and RecordLink projection remains as defined by 020. Convention consumers MUST check every raw field occurrence, including malformed links omitted from RecordLink facts. Lace does not automatically acquire links or assemble files and directories.

Chunk sequences describe complete file bytes

A chunk node is an H3 Plex with empty data, exactly one Kind: chunks, one or more Chunk+Link occurrences, and no other application fields. Each link targets a canonical H3 Blob or another chunk-node Plex; Seals, unsupported formats and other Plex shapes are invalid. A file may instead be a bare Blob; an empty file is an empty Blob. Example application fields, with symbolic target hashes:

Chunk+Link: 0..3 B.<first>.H3
Chunk+Link: 3..8 P.<remaining>.H3
Kind: chunks

Link data is exactly start..end. Both endpoints are canonical unsigned decimal integers in 0..=u64::MAX, with no signs, whitespace or leading zeros except 0. Convention readers MUST require positive half-open ranges, starting at zero and contiguous in occurrence order. Each range length equals its child’s complete logical length: a Blob contributes only its data, and a chunk node contributes its recursively described byte sequence. Child offsets start at zero; a parent range relocates the whole child, not a slice. The final end is the node’s positive logical length, not its structural Data-Length.

Every child and length MUST validate before the file is available. Missing children, invalid shapes and ranges, or arithmetic overflow cannot produce a partial file. Repeated child references at separate ranges are valid sharing. One Kind leaves at most 511 links per node; additional levels describe larger files without changing the 32 MiB per-Blob limit. Chunking is hash-significant; equal assembled bytes do not make different chunk trees identical records.

A directory root or page is an H3 Plex with empty data, exactly one Kind: directory, and only these additional application fields:

Field Link data Canonical H3 target
File+Link filename component Blob or chunk-node Plex
Child+Link directory-name component directory Plex
Page+Link local decimal ordinal directory Plex

An empty directory has only Kind: directory. Example application fields:

Child+Link: assets P.<child>.H3
File+Link: index.html B.<file>.H3
Kind: directory
Page+Link: 0 P.<page>.H3

A name is one nonempty UTF-8 NFC component, neither . nor .., containing no / or C0/DEL control bytes. Accepted names preserve their exact bytes, including leading and trailing spaces. H3 target hashes occupy 48 ASCII bytes, so the 128-byte complete-line limit permits 68 filename bytes for File+Link and 67 directory-name bytes for Child+Link. These are UTF-8 byte limits, not character counts; consumers MUST NOT trim, normalize, truncate or alias names.

Within each record, File+Link occurrences sort by filename bytes and Child+Link occurrences by directory-name bytes. Convention readers MUST reject decreases in parsed names, not compare complete link values. Each Page+Link ordinal is canonical unsigned decimal text equal to its same-name occurrence index: 0, 1, and so on.

A logical directory unions its direct file/child entries with entries reached recursively through pages. Pages add no path component; a child starts a new logical directory. Names MUST be unique across the entire logical directory, including both entry kinds and all pages, even for repeated same-target declarations. There is no required name ordering between pages. Every referenced page, child and file MUST exist and validate; missing entries cannot be silently omitted. Unsupported hash formats and wrong target families are invalid rather than missing dependencies.

One Kind leaves at most 511 links per record. Pages may reference further pages; the first record’s capacity does not bound a logical directory. Reusing a child subtree under different names is valid and expands at each placement. Page layout is hash-significant even when two layouts describe the same names/files. Consumers bound reference depth and logical expansion, counting repeated uses rather than only distinct hashes, and reject cycles; those bounds do not change the generic H3 record limits. Release envelopes, publisher authority, path/resource limits and filesystem behavior remain consumer-owned.

Canonical structure determines containment

The only H3 canonical nesting is:

Blob: no contained record
Plex: one directly embedded Blob
Seal: one directly embedded Plex, which embeds one Blob

Canonical nesting validates the complete outer structure. Contained records become independently admitted records only through the admission behavior defined by Lace-020, which also defines containment fact projection.

Maximum H3 record size is bounded

At maximum Blob data length, a complete Blob is at most 33,554,510 bytes. A Plex adds one 55-byte glyph line and at most 512 field lines of 129 bytes each, for at most 33,620,613 bytes. A Seal adds one 55-byte glyph line, a maximum canonical 53-byte By line, and a 93-byte Mark line, for at most 33,620,814 bytes.

These maxima include all glyph lines, structural lines, blank lines, and data. They follow from the fixed H3 text widths and limits above; implementations MUST still validate structure rather than accepting a record merely because it fits the maximum.

A canonical vector covers all three record forms

The following complete records are byte-exact. Their five data bytes are hello; there is no LF after the final o. App, Group, Name, and TAI are ordinary application names. The two App occurrences retain caller order (chat, then archive) even though their values would sort differently.

Blob:

🖧: B.kQIYZC1_~Q_ji4~Twe2_7m9Zn2scg_Cu9zNTkbk9zq8.H3
: 

hello

Plex:

🖧: P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3
App: chat
App: archive
Group: room7
Name: note
TAI: tomorrow
🖧: B.kQIYZC1_~Q_ji4~Twe2_7m9Zn2scg_Cu9zNTkbk9zq8.H3
: 

hello

Seal over that exact Plex:

🖧: S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3
By: V.8qgOfsaEJkK1tzX51sTT9NKdM8J2SrzQq6k8cRPzvP0.H3
: t9QAUgyE1c391D6RsPH_gok2OXEPTRfcp4HOa_Wcyx8143adV34c1ReKeGmWXXn6ictYPxDz5ULgqz0RK4sbPl
🖧: P.roNBC0l3CG4ezmPY5F3H_pLBDBecaAYZRbOJ1aT7pqK.H3
App: chat
App: archive
Group: room7
Name: note
TAI: tomorrow
🖧: B.kQIYZC1_~Q_ji4~Twe2_7m9Zn2scg_Cu9zNTkbk9zq8.H3
: 

hello

The Seal uses the 009 secret payload consisting of 32 07 bytes and an auxRand32 consisting of 32 01 bytes. Its Mark subject is the decoded digest from the displayed Plex glyph.

Boundary and rejection examples

A complete field line of 128 bytes is valid, while 129 bytes is invalid. For example, X: plus 125 ASCII a bytes is accepted and X: plus 126 is rejected. The limit counts UTF-8 bytes: X: plus 62 NFC é characters and one ASCII a is 128 bytes and accepted; adding one ASCII byte is rejected.

A Plex with one application field is valid; a Plex whose glyph is followed immediately by an embedded Blob has zero fields and is invalid. A Plex with 512 field occurrences is valid; 513 is invalid. Repeated names are valid and their order is significant. These lines are valid in this order:

Label: second
Label: first
Mode: public

Reversing Label and Mode is invalid because Mode sorts after Label. Sorting the two Label values is neither required nor permitted as a constructor canonicalization substitute for preserving caller order.

In the canonical vector, moving Group: room7 before either App line and recomputing the Plex and any outer Seal hash is still invalid because Group to App decreases by field-name bytes. Replacing its Mark with the canonical 009 known-answer Mark gZnQE_RNHTpWu8N~RYMGMTjGo~CTh6qutD7ZfmssgOGH8E_wSP3cEf_Q6QGOGpC2Pkl_~4lVP9NtD5gsNxrxhG and recomputing the Seal hash is also invalid: that Mark has the right form and key but was made over a different digest. Replacing the embedded Plex with the displayed Blob and recomputing the Seal hash remains invalid because a Seal cannot wrap a Blob.

Implementations also reject CRLF, non-NFC line text, Data-Length: 01, a reserved application field name, decreasing field-name order, a mismatched outer or embedded digest, a Seal missing or reordering By or Mark, an invalid By or Mark, a Mark made over the Blob or Seal digest, a Blob over 32 MiB, or trailing bytes after a complete record.