The kernel under its own name: meta.integer_type
Status: landed differently in Revision 39.
- Proposed: step 7 of
one-level.md’s order of work. The kernel’s declarations go under one nested schema map,meta, so that a schema importing it reserves one name. Each map is closed, declared in one document (rule A below). A bare name inside a map resolves there first (lexical scope), and a synthetic is homed by the innermost map.core.tnre-exports the few constructors a data schema writes as aliases, and the two kernel documents merge into one. - What happened: the nested
!schemamap, qualified names and qualified tags landed, and the kernel is one document. The rest was built differently:- Six concept maps and a root vocabulary. The kernel holds
meta(the type system),atom(the datatype properties), andnumeric,unicode,netandbinary(its atom constructors by family). Thirteen names an author writes stay at its root:void,boolean,integer,int64,text,bytes,iri,iri_reference,regex,doc,enum,setandschema. - Maps are open (rule C, not A). A map is the union of every document’s members. numeric.tn and net.tn add
their constructors to the kernel’s
numericandnet, and temporal.tn declarestemporal. - Every name resolves from the root. There is no lexical scope, so there is no shadowing rule. Synthetics are minted at the root, identified by their form.
- No re-exports. The data-format libraries each import the kernel alone. core.tn imports the three and declares
only the author annotations, no aliases.
meta.ref<T>andmeta.declaredare written by path,kindandscopedare gone, anddoc,setandenumsit at the kernel’s root.
- Six concept maps and a root vocabulary. The kernel holds
- Where: [TSON-SCHEMA] §2.2.3, §3.4, §4.1, §5.3, §5.12, §6, §8–§10, §12.1, §13.2; [TSON-DATA] §3.1, §4, §7.4.
implementation-feedback.md #14 (every
!schemavalue declares, from the root) and implementation-feedback.md #15 (the roots hold the common vocabulary), with the kernel’s own layout in the@docthat opensspec/m/meta.tn.
It answers decision 2 of one-level.md (“Decisions before the ladder goes”, which names a schema may
use) for the kernel’s footprint. It rides on two things one level brings: qualified names at type positions (“The
reference atom”, step 6), and the rule that a namespace’s members are types or values by what built them (“Entries are
types or values”). It was written against construction-operator.md’s <- spelling,
which was dropped. Read c <- { … } below as !c { … }.
The problem
One level makes core.tn import meta.tn, which imports the kernel, and imports are transitive (§2.2.3). Every name
the kernel and meta.tn declare is then reserved in every schema that imports core: record, map, set,
template, choice, namespace, reference, and every *_type constructor. one-level.md records the cost: Revision
38 moved five network names out of core because mac and hostname are plausible schema type names, and record,
template and map are far more plausible ones. Its two candidate answers, non-transitive imports and local
shadowing, are rules about imports in general, and each weakens a rule Revision 38 relies on.
The names at risk are all constructors. They are the rare vocabulary: a schema that describes data names integer,
text, date and uuid on most lines and integer_type almost never. Core’s instances are the names every schema
wants, and they are reserved today without complaint.
The proposal
The kernel’s declarations sit under one entry, a schema map nested in the document:
{
meta => !schema {
top => { }
type => top & { … }
atom => type & { … }
integer_type => atom & { … }
integer => integer_type <- {}
record => product & { … }
schema => namespace & { key_type: = type_name value_type: = top }
…
}
}
A schema that extends the kernel or applies a constructor qualifies the name; a schema that uses core’s instances does not:
integer => meta.integer_type <- {} -- in core.tn
uuid_type => meta.atom & { … } -- a kernel extension, visible as one per entry
order => { sku: text qty: uint8 } -- a data schema: nothing changes
pick => { t: kind<type> } -- or meta.kind<meta.type>, under "What core re-exports"
As built: the instances an author writes stay at the kernel’s root, not inside meta. Its constructors are split
by family, so the first line is integer => !numeric.integer_type {} at the kernel’s root, and net.tn declares
uuid => !net.uuid_type {}. The data schema is unchanged, and the last line is pick => { t: meta.ref<meta.type> },
with no kind (implementation-feedback.md #11, #15).
The construct already exists. Revision 38’s kernel declares schema => namespace & { key_type: = type_name value_type: = top }: the type of a schema document’s own root map. meta => !schema { … } is that map nested as an
entry, not a new kind of namespace. What changes is one sentence of §4.1, that a namespace value’s entries are values,
“nothing in it lifting or minting”. Under one level the rule becomes:
The rules for a schema document’s root map apply to every value of
schema. Its members are declarations: they lift and mint, they may be types, constructors or values, and a qualified name reaches them at any position a bare name is admitted.
As built: that sentence is implementation-feedback.md #14’s proposed §4.1 text, almost word for word. A value of any other
namespace type stays a value map. The kernel’s map type is meta.schema, and its root declares schema => meta.schema,
an alias, so !schema names the pre-loaded kernel’s whatever a document imports or declares (implementation-feedback.md #15).
The document root is then the outermost !schema, and the kernel’s bootstrap is the same loop one level deeper:
meta => meta.schema <- { … }, closed by pre-loading as now. slots.md’s reading, a schema as an object
whose slots may hold objects, is this taken literally.
What it buys.
- The kernel’s reserved footprint in an importing schema is one name,
meta. - A kernel extension is visible per entry with no header: a document that composes
meta.top,meta.atomormeta.productis extending the kernel, and says so where it does, which is what one-level.md’s decision 1 relies on in place of an opt-in. - The elided constructions are unchanged: a bare
{ … }ismeta.record <- { … },[A B]ismeta.enum <- [A B], and{K => V}ismeta.map <- { … }. An author who never writesmeta.never meets it.
As built: the footprint is the six map names and the thirteen root names. A schema importing core.tn also reserves
temporal and the libraries’ roots. record, identifier, type_ref, field_name, bounded, value, kind and
core stay free. The elided constructions target meta.record, meta.array, meta.map, meta.tuple, meta.choice
and meta.alias. An enumeration is !enum [A B], enum sitting at the kernel’s root, so there is no meta.enum
(implementation-feedback.md #15).
What it needs
In order of weight, with what became of each.
1. Qualified names at type positions. type-name = identifier (§12.1), and type_kind holds one segment. A <-
operand, a supertype, a field type and a kind slot must admit meta.integer_type. This is one-level.md’s step 6, the
one reference atom over a qualified name, which precedes this note’s step 7, and reference.md is its
grammar: kind<T> and ref<T> are already one atom there, so a qualified name at a type position is that atom with
of in the type family. Under the ladder it is the one new thing, and it is why this step follows the ladder’s
removal. As built: landed. A qualified name stands at every type position, as a reference’s of and as an
application’s head. meta.reference_type.of and a chain’s supertypes and subtypes are typed meta.type_ref. A
declared key stays one identifier (implementation-feedback.md #14).
2. Lexical scope inside a !schema body. Inside meta, integer_type must name its sibling bare, or the kernel
writes meta. on every line. The rule: a bare name resolves in the innermost enclosing !schema map first, then each
enclosing map outward, then the document’s imports. It is Newspeak’s rule for nested classes, and the one slots.md
proposes for parents; a name found at two depths is the inner one, with no diagnostic, since the inner declaration is
the one the author can see.
As built: not adopted. Every name resolves from the root: the first segment names an entry of the root namespace,
and each further segment selects a member. Inside geo, a sibling is written geo.address, and a bare address
names the root’s address or fails. The bundled documents write paths throughout (integer => !numeric.integer_type {}, format: numeric.ieee_format). The cost the rule was meant to avoid turned out to be small once the kernel was
split into six maps by concept, with its common vocabulary at the root. Relative lookup would also need a way to say
“from the root” and a shadowing rule, silent or diagnosed. Lexical scope stays open (implementation-feedback.md #14).
3. Synthetics and identity. A minted entry (array_text_1_3600e7d5) needs a home: the innermost enclosing
!schema of the declaration that lifted it. §10’s identity of an entry becomes !!id plus a path rather than a name,
and §8.2’s one-entry-per-form rule holds per map, two maps minting the same form being two entries, as two schemas
are today.
As built: synthetics mint at the root, one entry per form across the document, sugar inside a nested map included.
A dotted name contributes its text with each dot written _ (array_geo_inner_tag_…). A path under the kernel’s
meta contributes its member’s name (array_record_…), and one under another kernel map keeps its first segment. A
declared entry’s identity is its declaring document and its path, as proposed. A synthetic’s is its form, so two
documents minting one form declare one entry. Homing by the innermost map waits on lexical scope, since a name minted
inside geo would have to resolve where it is written, and stays open with it (implementation-feedback.md #14).
4. Qualified tags in data. A resolved form is a data document, and it describes a type as !record { fields: … }.
Under this proposal that is !meta.record { … }. The lexer already takes meta.record as one unquoted token, but
[TSON-DATA] §7.4’s tag production has to admit the dot, and a schemaless read has to leave a dotted tag unresolved
rather than refuse it. That is a Part 1 edit, made in reference.md (“Qualified tags”). As built:
landed as "!" qualified-name. An annotation’s name became "@" qualified-name on the same terms, so the kernel’s
annotations are written @atom.ordering:TOTAL and @meta.disjoint (implementation-feedback.md #14, #15).
5. Extension: who may add to meta. Decided at the time (“Extending the kernel’s namespace” below): nobody. A
!schema value is declared in one document, as an entry is, and meta-kernel.tn and meta.tn become one document.
As built: anybody. Maps are open, and the one-document merge landed (implementation-feedback.md #14, #15).
6. What core re-exports. A handful of constructors are written by name in data schemas: kind<T>, ref<T>,
scoped and its instances, doc. Either they are spelled meta.kind<T> everywhere, or core.tn aliases them
(kind => meta.kind), an alias being the same entry and no sibling (§5.10’s sibling rule being deleted by one level).
Aliases, for the ones a data schema names; the list is short and is core’s to keep.
As built: no aliases. The list shrank until nothing was left to alias. kind and ref became one template,
meta.ref<T>, written by path (implementation-feedback.md #11). scoped reduced to meta.declared, also written by path. doc, set and enum, the constructors a data schema does write
bare, sit at the kernel’s root. core.tn declares no map of its own and holds only the author annotations
(deprecated, title, comment, examples, read_only, write_only) (implementation-feedback.md #15).
Extending the kernel’s namespace
uuid_type => meta.atom & { … } is a kernel extension, and it raises the question that decides this design: may a
document that imports the kernel add to meta? meta.tn is the first case, since it declares the atom constructors
the kernel does not. Written as it would first be tried:
!!import:"…/meta-kernel.tn"
{ meta => !schema { decimal_type => atom & { … } } }
Three rules could answer it.
A. Closed maps. A !schema value is declared in one document. The document above collides on meta with the
kernel it imports, refused as any duplicate entry is under §2.2.3; and inside its body bare atom would not resolve
anyway, since lexical scope reaches the kernel’s root, where atom is not, it being meta.atom. An extension is an
ordinary declaration composing meta.atom, wherever it lives, and nothing says “extension” because nothing needs to.
B. Parents on a schema map. meta => meta & { decimal_type => atom & { … } }: meta.tn’s meta has the kernel’s as
a parent, bare atom resolves through it, and an importer reaches meta.integer_type and meta.decimal_type by one
prefix, the first still the kernel’s entry by delegation. This is slots.md’s parent-slot proposal applied
to a !schema value, and it is the relation an import already is at the root, so it is one mechanism. Its cost is the
diamond. If core.tn and net.tn each extend meta, a schema importing both has two meta values that differ, which
Self refuses as ambiguous and which only a linearisation rule (C3) resolves. TSON has no such rule and should not want
one, and the series’ own libraries would be the first to hit it.
C. Open namespaces. Several documents contribute members to one meta, the member set being the union. This
reproduces the flat-namespace collision inside meta., found only when two extenders are imported together, and leaves
the identity of meta.decimal_type to the contributing document, so that meta. means different things in different
closures.
The direction taken in this note: A, and one kernel document. meta-kernel.tn and meta.tn are two documents only
because the ladder put the atom constructors a rung below the kernel. Under one level they import nothing but each
other and become one document, so the series’ constructors are under one meta. with no extension needed. net.tn’s
constructors go under net. (net.ipv4_type), honestly theirs, and a user’s uuid_type composes meta.atom at the
user’s root or under the user’s own prefix. Nothing wants to be under meta. except for a uniform spelling, and that is
not worth a merge rule. Should parents on a schema map ever return, it is slots.md’s question, weighed with the diamond
in view.
As built: C, with one kernel document. The merge landed: the kernel is meta.tn alone, pre-loaded and importing
nothing. The placement is what made C fit. The kernel’s own closure needs the integer, text, identifier, regex and iri
constructors, and it keeps the octet sequence beside them. So the kernel itself holds constructors of four families,
under numeric, unicode, net and binary. Their siblings live in the libraries: decimal_type in numeric.tn,
uuid_type in net.tn. Under A a family’s constructors would carry two prefixes by declaring document. Under C
numeric.tn and net.tn add their members to the kernel’s numeric and net, and an importer sees one map holding both
documents’ members. C’s two costs,
weighed above, are answered this way:
- Identity. A member’s identity is its declaring document and its path, so a user’s
meta.code_typeis no kernel entry. - Collision. A member path two documents declare is §2.2.3’s collision, found when both are imported, as predicted. Nothing reserves a map’s unclaimed paths for the document that declared it first. That is recorded as open in implementation-feedback.md #14 and #15.
B was not taken up.
Resolved forms
As proposed, a resolved form of the kernel is { meta => !meta.schema { … } }, its entries’ bodies described under the
kernel’s qualified names. As built: !schema { numeric => !meta.schema { … } … meta => !meta.schema { … } … },
the six maps’ members in resolved form, with the thirteen root entries and the seven synthetics at the root. Each
library’s form holds its own map with its own members and none of the kernel’s. A body is tagged by its constructor’s
name (!meta.record, !enum, !numeric.decimal_type). A library’s resolved form holds instances of constructors its
own source declares, so it still reads back only beside its source (implementation-feedback.md #4, #15).
The alternative, and why not here
Importer-side qualification, !!import:"…/meta.tn" as m then m.integer_type, is what most module systems do, and it
is the general answer to two user libraries colliding. It is the wrong tool for the kernel. Resolved output carries no
import list (§8.1), so an alias has no canonical spelling in it and every ingest would need the importer’s alias table
beside the source’s header. The collision one-level.md measured is the kernel’s footprint, and exporter-side naming
removes it without touching any importer. For user libraries the same answer holds, by one-level.md’s decision 2: a
library groups its declarations under its own !schema entry, and two libraries colliding at the root is an error, so
import aliasing is not needed there either. As built: as argued. A user library declares geo => !schema { … },
and no import alias exists.
Outcome for one-level.md’s decisions
- Decision 2 is answered by exporter-side naming. The kernel’s footprint is its six map names and thirteen root
names, and a user library makes room under its own
!schemaentry. Neither non-transitive imports nor local shadowing was needed. - Decision 1 needs no header opt-in: an extension is spelled where it happens, composing a
meta.constructor. One part is unfinished. A constructor composing a base kind directly makes a new form, and the first implementation backs only the forms the bundled documents declare, refusing an instance of any other (implementation-feedback.md #4, #15). - “The reference atom” gained its use: a qualified name at a type position is why it was needed.
- Decision 3 is unchanged:
meta.namespacestays a constructor, andmeta.schemais one of its instances.
Settled and open
- Merging the two kernel documents. Settled: one document,
meta.tn(https://tson.io/2026/39/m/meta.tn). core.tn keeps its name as the bundle importing numeric.tn, temporal.tn and net.tn (implementation-feedback.md #15). - Scope and shadowing. Moot while every name resolves from the root. It returns with lexical scope, which is open (implementation-feedback.md #14).
- Who may contribute to a map. Open: any document may, the collision rule guarding a member only by its being declared first (implementation-feedback.md #14, #15).
- Which names sit at a root. Open: the bundled placement is a judgement per entry, “what an author writes” being the intent but not yet a test an entry can be put to (implementation-feedback.md #15).