docs / Especificación 1.1Specification 1.1
Traducción informativa al español; el texto normativo es el original en inglés.
Especificación .mini#
Versión: 1.1 · Fecha: 2026-09-17 · Estado: Estable; reemplaza a 1.0 y mantiene válido todo documento 1.0 (§13) · Licencia: MIT Autores: Adrián E. J. Palma Obispo, Erick J. Palomino Santa Cruz (Universidad Peruana de Ciencias Aplicadas)
1. Resumen#
.mini es una notación de texto posicional, orientada a líneas y bifurcable para
salidas estructuradas producidas por modelos generativos de lenguaje en dominios
cerrados. Un documento .mini es una línea de cabecera que nombra un contrato
(un prefijo de familia) y declara el recuento de registros, seguida de exactamente
un registro por línea cuyos campos están separados por | y cuyo significado lo da
la posición. Como emisor y receptor comparten el contrato, el documento nunca repite
nombres de campo, llaves, corchetes, comillas ni indentación; su coste estructural es
el mínimo necesario para mantener cada registro independientemente validable y
convertible de forma determinista a un objeto JSON canónico.
.mini no es un formato único sino una familia de contratos gobernada por un
protocolo de bifurcación. Cualquier dominio orientado a registros — ítems de
evaluación, tarjetas, rúbricas, ítems de encuesta, casos de prueba, eventos de log,
anotaciones de entidades, filas de catálogo, salidas de clasificación, historias de
usuario — obtiene su propio contrato sin escribir un parser: la implementación de
referencia interpreta el contrato.
2. Conformidad#
Las palabras clave MUST, MUST NOT, SHOULD y MAY se interpretan como en RFC
2119. Un parser conforme acepta todo documento válido bajo un contrato,
rechaza todo documento inválido con al menos uno de los códigos de error de §8, y
produce el objeto canónico de §7. Un serializador conforme produce, para cada
objeto canónico válido bajo un contrato, un documento que el parser devuelve a un
objeto igual (§9, ida y vuelta), y rechaza todo objeto que no sea válido bajo el
contrato con el código de error de §9. Una familia conforme satisface los
invariantes de §10. La suite de conformidad (conformance/) fija el resultado
esperado de cada regla; las decisiones de diseño que sustentan las reglas, con su
evidencia, se registran en docs/adr/.
3. Estructura léxica#
3.1 Codificación y líneas#
Un documento es texto UTF-8. Los parsers MUST ignorar una marca de orden de bytes inicial y opcional. Las líneas se separan con LF (U+000A); los parsers MUST ignorar un CR (U+000D) inmediatamente anterior a un LF. Las líneas vacías o que solo contienen blancos no son significativas y MUST omitirse. La primera línea significativa es la cabecera; cada línea significativa siguiente es un registro.
3.2 Caracteres estructurales#
| Carácter | Papel | Alcance |
|---|---|---|
\| |
separador de campos | cabecera y registros |
separador de lista (, por defecto; un carácter fijado por el contrato) |
separa elementos de una lista, lista marcada o tupla | solo dentro de campos de tipo lista |
* |
marcador de selección | solo como último carácter de un elemento de lista |
\ |
carácter de escape | en todas partes |
" |
delimitador de elemento entrecomillado | solo como primer carácter de un elemento de lista |
= |
separador clave/valor | entradas de cabecera, primera ocurrencia no escapada |
Ningún otro carácter tiene significado estructural. Los dos puntos, corchetes, llaves, espacios y tabuladores son contenido literal; una comilla que no sea el primer carácter de un elemento de lista es literal.
3.3 Secuencias de escape#
| Secuencia | Denota |
|---|---|
\| |
una barra vertical literal |
\, |
una coma literal, sea cual sea el separador de lista (por tanto, un separador de lista literal cuando el separador es ,) |
\<sep> para el separador del contrato |
un separador de lista literal |
\* |
un asterisco literal (necesario solo cuando un elemento terminaría en *) |
\" |
una comilla literal (necesaria solo cuando un elemento empezaría por ") |
\\ |
una barra invertida literal |
\n |
un salto de línea dentro de un valor |
Los parsers MUST reconocer las secuencias de escape en toda posición. Un generador MUST escapar
|, \ y los saltos de línea en cada valor, y MUST proteger el separador de lista
y un * final dentro de elementos de lista, ya sea con los escapes de arriba o con
comillas (§3.4). Escapar el separador de lista en un campo escalar es innecesario
pero inofensivo (el sobre-escape es idempotente). \, es válido en todo contrato,
también cuando el separador de lista es otro carácter (sobre-escapar una coma nunca
invalida un documento); \<sep> solo es válido para el separador propio del
contrato, de modo que \; en un contrato cuyo separador es , es E09. Una barra
invertida seguida de cualquier otro carácter, o una barra invertida al final, es un
error (E09).
3.4 Elementos de lista entrecomillados#
Un elemento de lista MAY entrecomillarse con comillas dobles, estilo CSV: "impacto, justicia
y evidencia"*. Dentro de las comillas el separador de lista y * son literales y una
comilla doble "" denota una comilla; el marcador de selección, si lo hay, sigue a
la comilla de cierre ("…"*) o, equivalentemente, la precede inmediatamente
("…*"); un asterisco literal en esa posición se escribe \*. Los escapes de barra
invertida siguen activos dentro de las comillas (| y \ aún deben escaparse). Una
comilla sin cerrar, o texto entre la comilla de cierre y el siguiente separador, es
un error (E09). Entrecomillar y escapar son notaciones equivalentes del mismo valor;
el serializador canónico emite la forma escapada.
Un elemento de lista vacío puede escribirse sin comillas (a,,b, o con un
separador final como en a,) o entrecomillado (""); ambas formas denotan la
cadena vacía. Un campo que contiene un único elemento vacío sin comillas no se
distingue de un campo vacío (§6), por lo que el serializador canónico escribe todo
elemento que sea la cadena vacía como "": [""] se emite como "" y vuelve a
analizarse como [""]. Es el único caso en que el serializador emite comillas.
3.5 Blancos#
Los blancos iniciales y finales de un campo, de un elemento de lista y de un valor de cabecera no son significativos y MUST recortarse. Los blancos internos se preservan. Los campos escalares nunca van entrecomillados.
4. Gramática#
document ::= header ( LF record )* LF?
header ::= prefix ( "|" entry )*
entry ::= key "=" value
prefix ::= [A-Za-z] [A-Za-z0-9_-]*
key ::= [A-Za-z_] [A-Za-z0-9_]*
record ::= value ( "|" value )*
value ::= ( char | escape )* -- may be empty
list ::= ( element ( SEP element )* )? -- interpretation of a list-typed value
element ::= ( bare | quoted ) "*"?
bare ::= value -- must not start with an unescaped '"'
quoted ::= '"' ( qchar | '""' | escape )* '"'
qchar ::= any Unicode scalar except '"', "|", "\", LF
escape ::= "\" ( "|" | "," | SEP | "*" | '"' | "\" | "n" )
char ::= any Unicode scalar except "|", "\", LF
Formas léxicas de los tipos escalares (§6), aplicadas al valor una vez resueltos los escapes y recortados los blancos exteriores:
int ::= "-"? DIGIT+
float ::= "-"? DIGIT+ ( "." DIGIT+ )? ( ( "e" | "E" ) ( "+" | "-" )? DIGIT+ )?
bool ::= "true" | "false" | "1" | "0"
date ::= DIGIT DIGIT DIGIT DIGIT "-" DIGIT DIGIT "-" DIGIT DIGIT
decimal ::= "-"? DIGIT+ ( "." DIGIT+ )?
DIGIT ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" -- solo ASCII
SEP es el separador de lista del contrato. La gramática es regular a nivel de
línea: un registro se reconoce con una sola pasada de izquierda a derecha que
resuelve escapes y divide por | no escapados; un campo de tipo lista se divide
después por SEP no escapados. No se necesita anticipación más allá de un carácter,
así que el análisis es determinista y lineal en la longitud de la línea.
5. Cabecera#
La cabecera es prefix|key=value|key=value….
prefixnombra el contrato (familia). MUST coincidir con un contrato registrado.nMUST estar presente y MUST ser igual al número de líneas de registro (E03/E04).vMAY estar presente y nombra la versión del contrato (por defecto 1).- Las demás claves las tipifica el contrato (escalar, lista o tupla). Las claves desconocidas se aceptan y se conservan como strings, lo que permite a los productores adjuntar procedencia (modelo, fecha, idioma, tema) sin cambiar el contrato.
- Una clave de cabecera MAY actuar como clave de recuento: un campo de lista
cuya entrada de contrato declara
count_key: "k"MUST tener exactamentekelementos en cada registro (E07). Esto convierte una colisión de delimitador dentro de una lista en un error detectable. - Una clave MUST NOT aparecer más de una vez en la cabecera. Cada aparición repetida es E12; cuenta la primera aparición (un parser tolerante conserva su valor).
- Un valor de una clave tipada que no coincide con su tipo se reporta en la línea
de cabecera con el código de esa violación (E06, E07, E10, E13), igual que en un
registro. En particular,
n=abco unn=vacío es E06, no E03: E03 significa quenno está. Sinestá presente pero es inválido, no se comprueba el recuento de registros (no hay E04). E12 queda para entradas sin=, claves requeridas ausentes y claves repetidas.
6. Registros y tipos de campo#
Un contrato define una lista ordenada de campos nucleares seguida de una lista
ordenada de campos de extensión. Un registro MUST contener cada campo nuclear
(E05) y MAY contener un prefijo de los campos de extensión; las extensiones
ausentes son null. Si v de la cabecera es menor o igual que la versión del
contrato, el registro MUST NOT contener más campos que los declarados (E05). Si la
cabecera del mismo prefijo declara un v mayor, el parser MUST validar léxicamente
el registro completo, decodificar el prefijo de campos conocido e ignorar únicamente
los campos finales desconocidos.
| Tipo | Forma textual | JSON canónico | Notas |
|---|---|---|---|
str |
texto literal | string | |
int |
-?[0-9]+ |
integer | min/max opcionales (E13); dígitos ASCII; se admiten ceros a la izquierda; sin + |
float |
número JSON | number | los valores enteros MAY omitir .0; se admiten ceros a la izquierda; +1, .5, 1., NaN, Infinity son E06 |
bool |
true / false |
boolean | 1/0 aceptados a la entrada; ninguna otra forma (yes, y, t, True) se acepta (E06) |
enum |
uno de los valores declarados | string | E10 en caso contrario |
date |
AAAA-MM-DD |
string | un día existente del calendario gregoriano proléptico, de 0001-01-01 a 9999-12-31 (E06); min/max opcionales como "AAAA-MM-DD" (E13) |
decimal |
-?[0-9]+(\.[0-9]+)? |
string | número exacto en base 10, sin exponente (E06); min/max opcionales como cadenas decimales o enteros, comparados exactamente (E13) |
list<T> |
e1,e2,… |
array | aridad min/max/count_key (E07) |
mlist<T> |
e1*,e2,… |
array más una clave hermana selected |
regla del marcador: exactly_one (por defecto), at_least_one, at_most_one, any (E08) |
tuple(a:T,b:U,…) |
a,b,… |
objeto {a:…, b:…} |
aridad fija (E07); un nivel de anidamiento sin sintaxis de anidamiento |
Un campo vacío denota null y solo es válido para campos opcionales (E06). Esto
vale para todo tipo: una lista, lista marcada o tupla opcional vacía es null (en una
lista marcada, ambas claves hermanas son null). Una lista o lista marcada
requerida vacía denota la lista vacía, sujeta a min y a la regla del marcador
(E07, E08); una tupla requerida vacía es E07. Por tanto, el valor canónico de una
lista opcional vacía es null, nunca []. Un campo declarado unique MUST NOT
repetir su valor dentro de un documento (E11).
Los elementos de listas y tuplas se decodifican con su tipo de elemento o de
componente. Un elemento de lista vacío es la cadena vacía para elementos str y un
error de tipo para cualquier otro tipo de elemento (E06; E10 para elementos
enum). Un componente de tupla vacío es null si el componente es opcional y E06 en
caso contrario.
date y decimal viajan como cadenas en el objeto canónico para que ninguna
implementación los convierta mediante coma flotante binaria o una zona horaria. El
date canónico es el propio texto. El decimal canónico elimina los ceros a la
izquierda de la parte entera y el signo de un valor cero, y conserva todos los
dígitos fraccionarios, porque la escala puede ser significativa (007.50 →
"7.50", -0.0 → "0.0"). En un contrato, min/max de un date son cadenas
"AAAA-MM-DD" y los de un decimal son cadenas decimales o enteros con valor
absoluto menor que 2^53; cualquier otro límite invalida el contrato (E20).
La lista marcada es el modismo que reemplaza a un campo "respuesta" separado: el
elemento seleccionado lleva un sufijo de un carácter, lo que mantiene la selección
pegada a su contenido. Para exactly_one/at_most_one el valor canónico de
selected es un índice (o null); para at_least_one/any es una lista ascendente
de índices.
7. Objeto canónico#
Un parser MUST producir:
{ "prefix": "<prefix>",
"header": { "n": <int>, "v": <int>, ...typed header entries... },
"<records_key>": [ { "<field>": <value>, ... }, ... ] }
records_key la declara el contrato (p. ej. items, cases). Los valores de
campos date y decimal son cadenas (§6). Una lista marcada
options con clave de selección correct produce dos claves hermanas
"options": [...] y "correct": <index>. Una tupla produce un objeto anidado.
Los números canónicos se comparan numéricamente (-1 ≡ -1.0).
8. Validación y códigos de error#
La validación es local (cada registro se comprueba en su propia línea) y global (recuento y unicidad). Un parser MUST reportar el número de línea 1-based de cada error. Los parsers estrictos recogen todos los errores y rechazan el documento; los parsers tolerantes devuelven los registros válidos junto con la lista de errores, lo que permite la recuperación parcial de salidas generadas largas.
| Código | Condición |
|---|---|
| E01 | falta la línea de cabecera |
| E02 | el prefijo de la cabecera no coincide con el contrato |
| E03 | la cabecera no trae n |
| E04 | número de líneas de registro ≠ n |
| E05 | el registro tiene menos campos que el núcleo, o tiene campos excedentes sin declarar una versión de documento posterior a la del contrato |
| E06 | el valor escalar (en un registro o en una entrada de cabecera tipada) no coincide con su tipo (§4, §6), o un valor requerido está vacío |
| E07 | aridad de lista / tupla fuera de min/max, o ≠ count_key, o ≠ tamaño de tupla |
| E08 | el recuento de marcadores viola la regla de la lista marcada |
| E09 | secuencia de escape inválida o barra invertida al final |
| E10 | valor fuera de la enumeración |
| E11 | valor duplicado en un campo unique |
| E12 | entrada de cabecera sin =, clave de cabecera requerida ausente o clave de cabecera repetida |
| E13 | valor numérico, decimal o de fecha fuera de min/max |
| E20 | el contrato mismo es inválido |
| E21 | invariante de familia violado |
| ## 9. Ida y vuelta |
Para cada contrato C y cada objeto canónico o válido bajo C:
parse(dumps(o, C), C) = o y dumps(parse(t, C), C) = t para cada documento t
emitido por el serializador. Esta propiedad — no la compacidad — es el criterio de
aceptación de una familia .mini: una compresión que no hace ida y vuelta es una
abreviatura, no una serialización.
Un serializador MUST NOT emitir un documento que el parser rechazaría. Ante un
objeto que no es válido bajo el contrato, MUST fallar con un error que lleve el
código que el parser reporta para la misma violación (§8) y la línea física que la
entrada ocuparía en la salida: 1 para la cabecera e i + 2 para el registro en la
posición i (base 0). Por ejemplo, un valor requerido nulo o ausente es E06, un
valor fuera de la enumeración E10, un valor fuera de min/max E13, una lista de
longitud incorrecta E07, una selección que incumple la regla del marcador o no es
un índice válido E08, un valor unique repetido E11 y una clave de cabecera
requerida ausente E12. Un serializador MAY aceptar equivalentes no canónicos de un
valor válido (por ejemplo [] para una lista opcional vacía, que escribe como campo
vacío y que vuelve a analizarse como null).
10. Protocolo de bifurcación#
Una familia es un contrato nuevo derivado de un padre. Conserva la analizabilidad
por construcción si satisface cinco invariantes, todos comprobables por máquina por
el registro de referencia (mini check-forks):
| # | Invariante | Regla |
|---|---|---|
| I1 | Línea local | una línea = un registro completo e independientemente válido |
| I2 | Cabecera | prefijo y n obligatorios; las claves de cabecera requeridas del padre siguen requeridas |
| I3 | Núcleo estable | la lista de campos de la hija empieza con la lista completa de campos del padre (núcleo + extensiones), mismos nombres, mismo orden, mismos tipos, mismo separador de lista |
| I4 | Extensión al final | los campos nuevos se añaden tras los heredados y son opcionales |
| I5 | Ida y vuelta | la hija incluye fixtures (valid.mini ↔ canonical.json, escaping.mini, casos negativos) que pasan §9 |
Consecuencias. Un parser de versión anterior lee documentos posteriores del
mismo prefijo cuando la cabecera declara un v mayor: valida la línea completa,
decodifica los campos que conoce e ignora la cola desconocida. Una bifurcación usa
otro prefijo: el registro se analiza primero con el contrato hijo elegido por el
despacho del registro y luego la aplicación MAY proyectar su prefijo heredado al
esquema del padre; esto no equivale a analizar directamente el documento hijo con
el contrato padre. El contrato hijo también admite registros sin sus extensiones
opcionales cuando se emiten bajo el prefijo hijo. Reordenar, retipificar o eliminar
un campo heredado es un cambio incompatible y MUST publicarse bajo un prefijo
nuevo, nunca como una versión nueva del mismo prefijo. El crecimiento compatible
(añadir extensiones o claves de cabecera opcionales) incrementa v.
Una familia se publica como una carpeta forks/<prefix>/ con contract.json,
README.md y fixtures/. El bloque de especificación que un modelo generativo
necesita para producir la familia se deriva mecánicamente del contrato
(mini prompt <prefix>); una familia, por tanto, consiste en datos, no código.
11. Fundamento del diseño#
- Posición en vez de nombres. En un dominio cerrado ambos lados conocen el
esquema; repetir
"statement","options","correct"en cada registro es sobrecarga pura. Llevar el esquema al contrato, declarado una vez, es lo que hace que el coste por registro se acerque al contenido mismo. - Un byte, un papel.
|nunca aparece dentro de listas, el separador nunca aparece a nivel de campo, y*es solo un sufijo. Esta estricta separación de niveles es lo que mantiene la gramática regular y el parser de una pasada. - Dos protecciones equivalentes para el separador de lista. La versión 0 (2026-06) usaba solo comillas estilo CSV; la validación generativa mostró que un modelo más débil a veces omitía las comillas y producía colisiones silenciosas de delimitador. Un borrador de la versión 1 reemplazó las comillas solo por escapes de barra invertida; una segunda ronda de validación mostró que la misma clase de modelo ignora un escape desconocido pero aplica con fiabilidad las comillas CSV, que ha visto en enormes cantidades de datos de entrenamiento. La versión 1.0, por tanto, acepta ambas notaciones (§3.3, §3.4), emite canónicamente la forma escapada, y añade la clave de recuento de §5, que convierte cualquier colisión residual en un error de aridad detectable en vez de una corrupción silenciosa.
- Marcador como sufijo. La selección viaja con su contenido, lo que evita desalineaciones índice/contenido durante la generación y permite la verificación local.
- Decisiones registradas. Cada regla anterior que implicó una elección (contrato
posicional, escapes y comillas, marcador, clave de recuento, modo tolerante,
bifurcación, cola de versión, las precisiones de 1.1 y los tipos
date/decimal) tiene un registro de decisión de arquitectura con su contexto, alternativas y evidencia endocs/adr/. - Evolución solo por añadido. La misma disciplina de los protocolos binarios y los
esquemas evolucionables (campos nuevos solo al final; los lectores anteriores
ignoran la cola únicamente si el mismo prefijo declara un
vposterior) da compatibilidad hacia adelante y hacia atrás sin negociación adicional.
12. Limitaciones (por diseño)#
.mini no representa jerarquías profundas, relaciones muchos-a-muchos dentro de un
registro, registros heterogéneos en un documento, o esquemas que evolucionan durante
una conversación. Las relaciones se expresan con el patrón relacional (una segunda
familia cuyos registros referencian identificadores de la primera, p. ej. registros
card que apuntan a ítems a) o con campos tuple de un nivel. Los estándares
formales de interoperabilidad (p. ej. QTI para evaluación) siguen siendo el destino
del objeto canónico, no del formato de transporte. Los ahorros de tokens dependen del
tokenizador y del idioma; el benchmark de referencia reporta el tokenizador, el corpus,
los serializadores y la línea base de cada figura.
13. Cambios en 1.1#
La versión 1.1 (2026-09-17) fija los puntos que 1.0 dejó sin definir y añade dos tipos escalares. Cada cambio tiene una regla normativa en las secciones anteriores, al menos un caso en la suite de conformidad y un registro de decisión.
| Punto abierto en 1.0 | Regla en 1.1 | Sección | ADR |
|---|---|---|---|
\, cuando el separador no es , |
siempre una coma literal; \<sep> solo para el separador del contrato |
§3.3 | 0009 |
Booleanos distintos de true/false/1/0, enteros con +, floats como .5 o 1. |
rechazados (E06); solo dígitos ASCII | §4, §6 | 0010 |
Código de un valor de cabecera mal tipado (n=abc) |
código de la violación de tipo (E06…), sin E03 | §5 | 0011 |
| Claves de cabecera repetidas | E12; cuenta la primera aparición | §5 | 0012 |
Elementos de lista vacíos (a,,b, separador final) e ida y vuelta de [""] |
la cadena vacía; se serializa como "" |
§3.4, §6 | 0013 |
| Lista opcional vacía | null (como todo campo opcional) | §6 | 0014 |
| Código de error del serializador ante objetos inválidos | el código del parser y la línea que ocuparía la entrada | §9 | 0015 |
| Fechas y decimales exactos | tipos nuevos date y decimal, JSON canónico string |
§6 | 0016 |
Compatibilidad. Un documento válido según 1.0 es válido según 1.1 y produce el
mismo objeto canónico. Los cambios solo afectan entradas cuyo resultado 1.0 no
definía: formas ajenas a la tabla de §6 que las implementaciones aceptaban
(yes, +5, .5), cabeceras con una clave repetida (cuyo objeto canónico 1.0 no
determinaba) y el código de error de documentos y objetos que ya eran inválidos.
\, con otro separador y los elementos vacíos sin comillas ya se aceptaban, y 1.1
fija su significado sin rechazarlos; una lista opcional vacía pasa a ser null, como
ya decía la frase de 1.0 «un campo vacío denota null». Los 303 casos de la suite de
conformidad 1.0 siguen en la suite 1.1 con las mismas expectativas; cinco casos del
serializador ahora también fijan el código de error, y el caso de contrato inválido
que usaba date como tipo desconocido ahora usa datetime. Un contrato 1.0 sigue
siendo un contrato 1.1 válido; un contrato que usa date o decimal requiere una
implementación 1.1.
Normative text; the Spanish version is an informative translation.
.mini Specification#
Version: 1.1 · Date: 2026-09-17 · Status: Stable; supersedes 1.0 and keeps every 1.0 document valid (§13) · License: MIT Authors: Adrián E. J. Palma Obispo, Erick J. Palomino Santa Cruz (Universidad Peruana de Ciencias Aplicadas)
1. Abstract#
.mini is a line-oriented, positional, forkable text notation for structured
outputs produced by generative language models in closed domains. A
.mini document is a header line that names a contract (a family prefix)
and declares the record count, followed by exactly one record per line whose
fields are separated by | and whose meaning is given by position. Because
sender and receiver share the contract, the document never repeats field
names, braces, brackets, quotation marks or indentation; its structural cost is
the minimum needed to keep every record independently validatable and
deterministically convertible to a canonical JSON object.
.mini is not a single format but a family of contracts governed by a
forking protocol. Any record-oriented domain — assessment items, flashcards,
rubrics, survey items, test cases, log events, entity annotations, catalogue
rows, classification outputs, user stories — obtains its own contract without
writing a parser: the reference implementation interprets the contract.
2. Conformance#
The key words MUST, MUST NOT, SHOULD and MAY are to be interpreted as in RFC
2119. A conforming parser accepts every document valid under a contract,
rejects every invalid document with at least one of the error codes of §8, and
produces the canonical object of §7. A conforming serializer produces, for
every canonical object valid under a contract, a document that the parser maps
back to an equal object (§9, round-trip), and rejects every object that is not
valid under the contract with the error code of §9. A conforming fork
satisfies the invariants of §10. The conformance suite (conformance/) fixes the
expected result of each rule; the design decisions behind the rules, with their
evidence, are recorded in docs/adr/.
3. Lexical structure#
3.1 Encoding and lines#
A document is UTF-8 text. An optional leading byte-order mark MUST be ignored. Lines are separated by LF (U+000A); a CR (U+000D) immediately preceding an LF MUST be ignored. Lines that are empty or contain only whitespace are not significant and MUST be skipped. The first significant line is the header; every following significant line is a record.
3.2 Structural characters#
| Character | Role | Scope |
|---|---|---|
\| |
field separator | header and records |
list separator (, by default; one character fixed by the contract) |
separates elements of a list, marked list or tuple | inside list-typed fields only |
* |
selection marker | only as the last character of a list element |
\ |
escape character | everywhere |
" |
quoted-element delimiter | only as the first character of a list element |
= |
key/value separator | header entries, first unescaped occurrence |
No other character has structural meaning. Colons, brackets, braces, spaces and tabs are literal content; a quotation mark that is not the first character of a list element is literal.
3.3 Escape sequences#
| Sequence | Denotes |
|---|---|
\| |
a literal vertical bar |
\, |
a literal comma, whatever the list separator (so a literal list separator when the separator is ,) |
\<sep> for the contract's separator |
a literal list separator |
\* |
a literal asterisk (needed only when an element would otherwise end in *) |
\" |
a literal quotation mark (needed only when an element would otherwise start with ") |
\\ |
a literal backslash |
\n |
a line break inside a value |
Escape sequences MUST be recognised in every position. A generator MUST escape
|, \ and line breaks in every value, and MUST protect the list separator
and a trailing * inside list elements, either with the escapes above or with
quoting (§3.4). Escaping the list separator in a scalar field is unnecessary
but harmless (over-escaping is idempotent-safe). \, is valid in every
contract, also when the list separator is another character (over-escaping a
comma never breaks a document); \<sep> is valid only for the contract's own
separator, so \; in a contract whose separator is , is E09. A backslash
followed by any other character, or a trailing backslash, is an error (E09).
3.4 Quoted list elements#
A list element MAY be enclosed in double quotes, CSV style: "impacto, justicia
y evidencia"*. Inside the quotes the list separator and * are literal and a
doubled quote "" denotes one quotation mark; the selection marker, if any,
follows the closing quote ("…"*) or, equivalently, immediately precedes it
("…*"); a literal asterisk in that position is written \*. Backslash escapes remain active inside quotes (|
and \ must still be escaped). An unbalanced quote, or text between the
closing quote and the next separator, is an error (E09). Quoting and escaping
are equivalent notations for the same value; the canonical serializer emits
the escaped form.
An empty list element may be written bare (a,,b, or a trailing separator as
in a,) or quoted (""); both denote the empty string. A field that holds a
single bare empty element cannot be told apart from an empty field (§6), so the
canonical serializer writes every empty-string element as "": [""] is
emitted as "" and parses back to [""]. This is the only case in which the
serializer emits quotes.
3.5 Whitespace#
Leading and trailing whitespace of a field, of a list element and of a header value is not significant and MUST be trimmed. Internal whitespace is preserved. Scalar fields are never quoted.
4. Grammar#
document ::= header ( LF record )* LF?
header ::= prefix ( "|" entry )*
entry ::= key "=" value
prefix ::= [A-Za-z] [A-Za-z0-9_-]*
key ::= [A-Za-z_] [A-Za-z0-9_]*
record ::= value ( "|" value )*
value ::= ( char | escape )* -- may be empty
list ::= ( element ( SEP element )* )? -- interpretation of a list-typed value
element ::= ( bare | quoted ) "*"?
bare ::= value -- must not start with an unescaped '"'
quoted ::= '"' ( qchar | '""' | escape )* '"'
qchar ::= any Unicode scalar except '"', "|", "\", LF
escape ::= "\" ( "|" | "," | SEP | "*" | '"' | "\" | "n" )
char ::= any Unicode scalar except "|", "\", LF
Lexical forms of the scalar types (§6), applied to the value after escapes are resolved and surrounding whitespace is trimmed:
int ::= "-"? DIGIT+
float ::= "-"? DIGIT+ ( "." DIGIT+ )? ( ( "e" | "E" ) ( "+" | "-" )? DIGIT+ )?
bool ::= "true" | "false" | "1" | "0"
date ::= DIGIT DIGIT DIGIT DIGIT "-" DIGIT DIGIT "-" DIGIT DIGIT
decimal ::= "-"? DIGIT+ ( "." DIGIT+ )?
DIGIT ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" -- ASCII only
SEP is the contract's list separator. The grammar is regular at the line
level: a record is recognised by a single left-to-right pass that resolves
escapes and splits on unescaped |; a list-typed field is then split on
unescaped SEP. No look-ahead beyond one character is required, so parsing is
deterministic and linear in the length of the line.
5. Header#
The header is prefix|key=value|key=value….
prefixnames the contract (family). It MUST match a registered contract.nMUST be present and MUST equal the number of record lines (E03/E04).vMAY be present and names the contract version (default 1).- Other keys are typed by the contract (scalar, list or tuple). Unknown keys are accepted and kept as strings, which lets producers attach provenance (model, date, language, topic) without changing the contract.
- A header key MAY act as a count key: a list field whose contract entry
declares
count_key: "k"MUST have exactlykelements in every record (E07). This turns a delimiter collision inside a list into a detectable error. - A key MUST NOT appear more than once in the header. Every repeated occurrence is E12; the first occurrence is the one that counts (a lenient parser keeps its value).
- A value of a typed key that does not match its type is reported on the header
line with the code of that violation (E06, E07, E10, E13), as in a record. In
particular
n=abcor an emptyn=is E06, not E03: E03 means thatnis absent. Whennis present but invalid, the record count is not checked (no E04). E12 is reserved for entries without=, missing required keys and repeated keys.
6. Records and field types#
A contract defines an ordered list of core fields followed by an ordered
list of extension fields. A record MUST contain every core field (E05) and
MAY contain a prefix of the extension fields; missing extensions are null. If
the header v is less than or equal to the contract version, a record MUST NOT
contain more fields than the contract declares (E05). If the same-prefix header
declares a higher v, the parser MUST lexically validate the complete record,
decode the known field prefix, and ignore only unknown trailing fields.
| Type | Text form | Canonical JSON | Notes |
|---|---|---|---|
str |
literal text | string | |
int |
-?[0-9]+ |
integer | optional min/max (E13); ASCII digits; leading zeros allowed; no + |
float |
JSON number | number | integral values may omit .0; leading zeros allowed; +1, .5, 1., NaN, Infinity are E06 |
bool |
true / false |
boolean | 1/0 accepted on input; no other form (yes, y, t, True) is accepted (E06) |
enum |
one of the declared values | string | E10 otherwise |
date |
YYYY-MM-DD |
string | an existing day of the proleptic Gregorian calendar, 0001-01-01 to 9999-12-31 (E06); optional min/max as "YYYY-MM-DD" (E13) |
decimal |
-?[0-9]+(\.[0-9]+)? |
string | exact base-10 number, no exponent (E06); optional min/max as decimal strings or integers, compared exactly (E13) |
list<T> |
e1,e2,… |
array | min/max/count_key arity (E07) |
mlist<T> |
e1*,e2,… |
array plus a sibling selected key |
marker rule: exactly_one (default), at_least_one, at_most_one, any (E08) |
tuple(a:T,b:U,…) |
a,b,… |
object {a:…, b:…} |
fixed arity (E07); one level of nesting without nesting syntax |
An empty field denotes null and is valid only for optional fields (E06). This
holds for every type: an empty optional list, marked list or tuple is null (for a
marked list, both sibling keys are null). An empty required list or marked list
denotes the empty list, subject to min and to the marker rule (E07, E08); an
empty required tuple is E07. The canonical value of an empty optional list is
therefore null, never []. A field declared unique MUST NOT repeat its value
within a document (E11).
List and tuple elements are decoded with their item or component type. An
empty list element is the empty string for str items and a type error for any
other item type (E06; E10 for enum items). An empty tuple component is null
when the component is optional and E06 otherwise.
date and decimal travel as strings in the canonical object so that no
implementation converts them through binary floating point or a time zone. The
canonical date is the text itself. The canonical decimal removes the leading
zeros of the integer part and the sign of a zero value and keeps every
fractional digit, because the scale can be meaningful (007.50 → "7.50",
-0.0 → "0.0"). In a contract, the min/max of a date are "YYYY-MM-DD"
strings and those of a decimal are decimal strings or integers whose absolute
value is below 2^53; any other bound makes the contract invalid (E20).
The marked list is the idiom that replaces a separate "answer" field: the
selected element carries a one-character suffix, keeping the selection attached
to its content. For exactly_one/at_most_one the canonical selected value
is an index (or null); for at_least_one/any it is an ascending list of
indices.
7. Canonical object#
A parser MUST produce:
{ "prefix": "<prefix>",
"header": { "n": <int>, "v": <int>, ...typed header entries... },
"<records_key>": [ { "<field>": <value>, ... }, ... ] }
records_key is declared by the contract (e.g. items, cases). Values of
date and decimal fields are strings (§6). A marked
list options with selection key correct yields two sibling keys
"options": [...] and "correct": <index>. A tuple yields a nested object.
Canonical numbers compare numerically (-1 ≡ -1.0).
8. Validation and error codes#
Validation is local (each record is checked on its own line) and global (count and uniqueness). A parser MUST report the 1-based line number of every error. Strict parsers collect all errors and reject the document; lenient parsers return the valid records together with the error list, which enables partial recovery of long generated outputs.
| Code | Condition |
|---|---|
| E01 | no header line |
| E02 | header prefix does not match the contract |
| E03 | header lacks n |
| E04 | number of record lines ≠ n |
| E05 | record has fewer fields than the core, or has excess fields without declaring a document version newer than the contract |
| E06 | scalar value (in a record or a typed header entry) does not match its type (§4, §6), or a required value is empty |
| E07 | list / tuple arity outside min/max, or ≠ count_key, or ≠ tuple size |
| E08 | marker count violates the marked-list rule |
| E09 | invalid escape sequence or trailing backslash |
| E10 | value not in the enumeration |
| E11 | duplicate value in a unique field |
| E12 | header entry without =, missing required header key, or repeated header key |
| E13 | numeric, decimal or date value outside min/max |
| E20 | the contract itself is invalid |
| E21 | fork invariant violated |
9. Round-trip#
For every contract C and every canonical object o valid under C:
parse(dumps(o, C), C) = o and dumps(parse(t, C), C) = t for every document
t emitted by the serializer. This property — not compactness — is the
acceptance criterion of a .mini family: a compression that does not round-trip
is an abbreviation, not a serialization.
A serializer MUST NOT emit a document that the parser would reject. Given an
object that is not valid under the contract, it MUST fail with an error that
carries the code the parser reports for the same violation (§8) and the physical
line the offending entry would occupy in the output: 1 for the header and
i + 2 for the record at 0-based position i. For example, a null or missing
required value is E06, a value outside the enumeration E10, a value outside
min/max E13, a list of the wrong length E07, a selection that breaks the
marker rule or is not a valid index E08, a repeated unique value E11 and a
missing required header key E12. A serializer MAY accept non-canonical
equivalents of a valid value (for example [] for an empty optional list, which
it writes as an empty field and which parses back as null).
10. Forking protocol#
A fork is a new contract derived from a parent. It keeps parsability by
construction if it satisfies five invariants, all machine-checkable by the
reference registry (mini check-forks):
| # | Invariant | Rule |
|---|---|---|
| I1 | Local line | one line = one complete, independently valid record |
| I2 | Header | prefix and n are mandatory; required header keys of the parent stay required |
| I3 | Stable core | the child's field list starts with the parent's full field list (core + extensions), same names, same order, same types, same list separator |
| I4 | Tail extension | new fields are appended after the inherited ones and are optional |
| I5 | Round-trip | the child ships fixtures (valid.mini ↔ canonical.json, escaping.mini, negative cases) that pass §9 |
Consequences. An older parser reads later documents of the same prefix when
the header declares a higher v: it validates the complete line, decodes the
fields it knows, and ignores the unknown tail. A fork uses a different prefix:
the record is first parsed with the child contract chosen by record dispatch,
after which the application MAY project its inherited prefix to the parent
schema; this is not direct parsing of the child document with the parent
contract. The child contract also accepts records without its optional
extensions when they are emitted under the child prefix. Reordering, retyping
or removing an inherited field is a breaking change and MUST be published under
a new prefix, never as a new version of the same prefix. Compatible growth
(appending extensions or optional header keys) increments v.
A fork is published as a folder forks/<prefix>/ containing contract.json,
README.md and fixtures/. The specification block that a generative model
needs to produce the fork is derived mechanically from the contract
(mini prompt <prefix>); a fork therefore consists of data, not code.
11. Design rationale#
- Position instead of names. In a closed domain both sides know the
schema; repeating
"statement","options","correct"in every record is pure overhead. Moving the schema to the contract, declared once, is what makes the per-record cost approach the content itself. - One byte, one role.
|never appears inside lists, the separator never appears at field level, and*is only a suffix. This strict separation of levels is what keeps the grammar regular and the parser one-pass. - Two equivalent protections for the list separator. Version 0 (2026-06) used CSV-style quoting only; generative validation showed that a weaker model sometimes omitted the quotes and produced silent delimiter collisions. A draft of version 1 replaced quoting by backslash escaping only; a second round of validation showed that the same class of model ignores an unfamiliar escape but reliably applies CSV quoting, which it has seen in vast amounts of training data. Version 1.0 therefore accepts both notations (§3.3, §3.4), emits the escaped form canonically, and adds the count key of §5, which turns any residual collision into a detectable arity error instead of a silent corruption.
- Marker as suffix. The selection travels with its content, which avoids index/content misalignment during generation and allows local verification.
- Recorded decisions. Every rule above that involved a choice (positional
contract, escapes and quotes, marker, count key, lenient mode, forking,
version tail, the clarifications of 1.1 and the
date/decimaltypes) has an architecture decision record with its context, alternatives and evidence indocs/adr/. - Append-only evolution. The same discipline used by binary protocols and
evolvable schemas (new fields only at the end; older readers ignore the tail
only when the same prefix declares a later
v) gives forward and backward compatibility without additional negotiation.
12. Limitations (by design)#
.mini does not represent deep hierarchies, many-to-many relations inside a
record, heterogeneous records in one document, or schemas that evolve during a
conversation. Relations are expressed with the relational pattern (a second
family whose records reference identifiers of the first, e.g. card records
pointing to a items) or with one-level tuple fields. Formal interoperability
standards (e.g. QTI for assessment) remain the target of the canonical object,
not of the wire format. Token savings are tokenizer- and language-dependent;
the reference benchmark reports the tokenizer, corpus, serializers and baseline
of every figure.
13. Changes in 1.1#
Version 1.1 (2026-09-17) fixes the points that 1.0 left undefined and adds two scalar types. Each change has a normative rule above, at least one case in the conformance suite and a decision record.
| Point left open by 1.0 | Rule in 1.1 | Section | ADR |
|---|---|---|---|
\, when the separator is not , |
always a literal comma; \<sep> only for the contract's separator |
§3.3 | 0009 |
Booleans other than true/false/1/0, integers with +, floats such as .5 or 1. |
rejected (E06); ASCII digits only | §4, §6 | 0010 |
Code of an ill-typed header value (n=abc) |
code of the type violation (E06…), no E03 | §5 | 0011 |
| Repeated header keys | E12; the first occurrence counts | §5 | 0012 |
Empty list elements (a,,b, trailing separator) and the round-trip of [""] |
the empty string; serialized as "" |
§3.4, §6 | 0013 |
| Empty optional list | null (as for every optional field) | §6 | 0014 |
| Error code of the serializer for invalid objects | the parser's code and the line the entry would occupy | §9 | 0015 |
| Dates and exact decimals | new types date and decimal, canonical JSON strings |
§6 | 0016 |
Compatibility. A document valid under 1.0 is valid under 1.1 and produces the
same canonical object. The changes only touch inputs whose result 1.0 did not
define: forms outside the §6 table that implementations happened to accept
(yes, +5, .5), headers with a repeated key (whose canonical object 1.0
did not determine), and the error code reported for documents and objects that
were already invalid. \, with another separator and bare empty elements were
already accepted, and 1.1 fixes their meaning without rejecting them; an empty
optional list becomes null, as the 1.0 sentence "an empty field denotes null"
already stated. The 303 cases of the 1.0 conformance suite remain in the 1.1
suite with the same expectations; five serializer cases now also state the error
code, and the invalid-contract case that used date as an unknown type now uses
datetime. A 1.0 contract remains a valid 1.1 contract; a contract that uses
date or decimal requires a 1.1 implementation.