Tags: record, stored-format
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.
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.
🖧: <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.
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.
A field line has this exact syntax:
<Name>: <value>\n
Implementations MUST enforce these rules:
:, space, or tab;:;0x00..0x1F and 0x7F are
forbidden in name and value bytes; the terminating LF is structural and
excluded from this prohibition;<Name>: <value> is at most 128
UTF-8 bytes, excluding LF; andThe structural Data-Length, By, and
Mark lines satisfy the same line and text rules in addition
to their form-specific rules below.
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:
Data-Length uses ASCII decimal digits
[0-9] with no leading zero except 0;<len> data bytes;
andBlob data is opaque and may contain any bytes, including bytes that resemble field lines, record glyphs, or ILTP items.
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:
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 :.
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:
By appears first and contains canonical
V.<b64a>.H3 verification-key text defined by
009;Mark appears second and contains one canonical 009
Mark;P.<b64a>.H3 glyph;By; andA 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.
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:
<link-data> is non-empty;<target-hash> is
exactly one ASCII space;<target-hash> is typed record-hash text beginning
with B., P., or S.;H3;<link-data> is application-defined text and may
contain spaces.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.
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.
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.
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.
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.
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.