Skip to content

Commit 35c4ab1

Browse files
committed
normative: graduate the meta-table into canonical-encoding and vectors
Add "The meta-table" section: the three-table floor closure, the recursive type descriptor, ref resolution by code and version, the pinned codes with the private range, and the fixed-point property. Presence is boolean-aligned (required 1, optional 0) but an extensible enum. Pin content-id vectors for the three floor tables; the encode form is dropped, since the floor is embedded and hashed rather than encoded from a value, and the bytes are regenerable. Signed-off-by: Chris Raynor <chris@raynor.tech>
1 parent ec5d436 commit 35c4ab1

4 files changed

Lines changed: 76 additions & 0 deletions

File tree

canonical-encoding.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,61 @@ This is the lesson of proto3, which removed the required field its predecessor c
205205

206206
One half of the presence question Murmur settles in its own favour. An absent field is omitted entirely, never encoded as a default value or a null (Booleans, and the absence of null, above), so "present with the default" and "absent" are never two readings of one state. That ambiguity is what forced an explicit presence marker back into proto3 after it was removed. The canonical form does not have it to fix.
207207

208+
## The meta-table
209+
210+
The schema model above rests on one artifact that has no schema outside itself: the **meta-table**, the field table that describes a field table. It is the fixed point that ends the regress of what decodes the table (The floor is shipped, not fetched, above). On the wire it is canonical CBOR like any artifact, an integer-keyed map under the rules of this document, and it is authored in YAML or Starlark and compiled, like any definition (specification Section 7.1). The field names it carries live in its own bytes, because a field table is data about fields, but those names never appear in an instance of the type it describes (Structure on the wire, meaning in the schema, above).
211+
212+
### The closure
213+
214+
The floor is not one table but a closure of three, each a field table, each described by the meta-table, with the meta-table describing itself. A field table is a map of three fields:
215+
216+
```
217+
key name type presence
218+
0 describes int required
219+
1 version int required
220+
2 entries array of (ref: entry) required
221+
```
222+
223+
`describes` is the artifact-type code this table is the grammar for, `version` is the format version it belongs to, and `entries` are its field entries. An entry is a map of four:
224+
225+
```
226+
key name type presence
227+
0 key int required
228+
1 name text required
229+
2 type ref: type-descriptor required
230+
3 presence int required
231+
```
232+
233+
and a field's type is a small recursive descriptor:
234+
235+
```
236+
key name type presence used when
237+
0 kind int required always
238+
1 of ref: type-descriptor optional kind = array (the element type)
239+
2 ref int optional kind = ref (the artifact-type of the nested table)
240+
3 unit int optional kind = decimal or rational
241+
```
242+
243+
A scalar is a kind alone. A magnitude adds a unit code, fixed per field so it never rides on the wire (Units, above). An array adds its element type. A nested structure is a ref to another table. The three tables reference one another, and themselves, so the set decodes itself and nothing outside it is needed.
244+
245+
### References resolve by code, and by version
246+
247+
A ref names a type, never bytes and never a version. It resolves to the table of the named artifact type at the format version of the artifact being read, which is stamped once on the top-level artifact and inherited down (specification Section 7.1). References inside the floor are by artifact-type code rather than by content address, which is what frees the closure from a content address that would otherwise depend on itself: the type-descriptor's `of` refers to the type-descriptor table, so a hash-based reference would chase its own tail. A node resolves a code from the tables it ships, as a language resolves its built-in types by name. The closure's content addresses still exist, for attestation, off the decode path. Tables compose by code, data composes by hash (the separation law, specification Section 1).
248+
249+
### Pinned codes
250+
251+
The codes follow the wire cost (Minimal encoding, above). The range 0 to 23 is one byte and holds the hottest standard codes, 24 to 255 is two bytes, 256 to 1023 is three bytes reserved for standard growth, and 1024 and above is the private range, where a custom protocol bears its own cost. The assignments are append-only (Grammar and vocabulary, above).
252+
253+
- **type-kinds**: int 0, bytes 1, text 2, bool 3, decimal 4, rational 5, array 6, ref 7.
254+
- **presence**: required 1, optional 0, boolean-aligned (required is the truthy value) but an extensible enum, with room for `required-to-act` and the like as appended codes.
255+
- **artifact-types**: field-table 0, entry 1, type-descriptor 2, then the protocol artifact types from 3, with 1024 and above reserved private.
256+
257+
Field keys are dense within each table, as shown above.
258+
259+
### The fixed point
260+
261+
Encoded under these codes, the three tables are the suite's most load-bearing fixture. Each decodes and re-encodes to identical bytes, the decoder refuses every non-canonical form, and decoding the meta-table yields exactly the field layout the meta-table is itself encoded in. The floor proves itself a fixed point. The bytes and their content addresses are pinned by the conformance vectors.
262+
208263
## Signed envelope and identifier
209264

210265
A signed artifact is claims plus a proof of who authored them. The claims are a canonical-CBOR map under the rules above. The proof is a signature over those claims by a key the reader can name and trust. This section pins the envelope that joins the two, and the identifier that names the key. It is the one part of the canonical form where a wrong byte boundary is a forgery and not a mismatch, so the boundary is stated exactly. The envelope is a minimal owned shape, a CBOR Web Token in all but the COSE wrapper (specification Section 7.1). COSE was declined as heavier than the requirement and unable to enforce the exactly-one-encoding rule that content-addressing needs.

vectors/content-id/entry.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"kind": "content-id",
3+
"description": "SHA-256 content address of the entry table, the field table for one field-table entry",
4+
"spec": "7.1",
5+
"cbor": "a3000101010284a4000001636b657902a100000301a4000101646e616d6502a100020301a4000201647479706502a2000702020301a40003016870726573656e636502a100000301",
6+
"sha256": "0a3091f67f7adcd0fbe4a27092fa87da7b02e2a2c663d6ae8eb0252cf4861a8b"
7+
}

vectors/content-id/meta-table.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"kind": "content-id",
3+
"description": "SHA-256 content address of the meta-table, the field table that describes a field table",
4+
"spec": "7.1",
5+
"cbor": "a3000001010283a40000016964657363726962657302a100000301a40001016776657273696f6e02a100000301a400020167656e747269657302a2000601a2000702010301",
6+
"sha256": "26aa46be63a790f075e63459b9f579ab33e8c084ea2be6b20587167362620220"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"kind": "content-id",
3+
"description": "SHA-256 content address of the type-descriptor table, the field table for a field type",
4+
"spec": "7.1",
5+
"cbor": "a3000201010284a4000001646b696e6402a100000301a4000101626f6602a2000702020300a40002016372656602a100000300a400030164756e697402a100000300",
6+
"sha256": "7092c4ee2d5b290e5f2fcce33f9d8ce99403d4a29344d4b5e33a04135c2611b1"
7+
}

0 commit comments

Comments
 (0)