schemaVersion:string = "1"
id:string = "appendix-canonical-form-v1"
title:string = "Appendix — Canonical AEON Form"
description:string = "Canonicalization conventions and normalization outputs for stable interchange."
created:date = 2026-03-25
modified:date = 2026-08-23
family:string = "appendices"
group:string = "Core Semantics"
standing:string = "official"
lifecycle:string = "published"
normativity:string = "informative"
license:string = "CC-BY-4.0"
path:string = "specification/appendices/appendix-canonical-form-v1"
order:number = 10
publish:boolean = true
keywords:list<string> = ["AEON", "canonical form", "canonicalization", "normalization", "stable interchange"]
related:list<string> = ["aeon-core-v1", "aeon-core-v1-value-types", "aeon-v1-supersession-table"]
bodyFormat:string = "and-v2"

body:prose = >`
  &ND v2

  # Appendix — Canonical AEON Form [# appendix-canonical-aeon-form]

  [* Appendix to:] AEON Specification v1


  ## Overview [# overview]

  Canonical AEON defines a deterministic textual representation suitable for hashing, diffing, signing, and reproducible builds.

  Canonical form is [* optional] for parsing but REQUIRED for canonical emitters.


  ## Document Structure [# document-structure]

  ### Header [# header]

  If no header exists, emit the default:

  ~~~$ aeon
  aeon:header = {
    encoding = "utf-8"
    mode = "transport"
    profile = "core"
    version = "1.0"
  }
  ~~~$

  Header fields sorted lexicographically.

  ### Top-Level Order [# top-level-order]

  1. [$ aeon:header] first
  2. All other bindings in lexicographic order

  This ordering is not formatter-only advice:

  - a structured header appearing after any body binding is invalid input;
  - canonicalization MUST reject such input rather than reordering it into a valid document.


  ## Keys and Objects [# keys-and-objects]

  - Object keys sorted lexicographically
  - Attribute keys sorted lexicographically
  - One key per line in multi-line objects

  Canonical layout is structure-sensitive:

  - inline object forms may remain inline when the enclosing canonical layout is inline;
  - when an enclosing object or list is rendered as multiline canonical layout, nested object values also canonically expand to multiline object blocks rather than remaining inline;
  - key sorting still applies at every level regardless of whether the object is rendered inline or multiline.


  ## Lists [# lists]

  - Element order preserved (never reordered)
  - Single-line for simple scalars, multi-line for complex values

  When a list is rendered multiline, complex object elements canonically expand to multiline object blocks.


  ## Scalars [# scalars]

  ### Numbers [# numbers]

  - Canonicalization of [$ :n] values is value-normalizing while preserving the
    broad representation family chosen by the author:
    - integer family
    - decimal family
    - exponent family

  - Remove all [$ _] separators
  - No leading [$ +] sign
  - Leading-dot decimals gain an explicit zero ([$ .5] → [$ 0.5])
  - Decimal-family values trim redundant trailing fractional zeroes, but retain at
    least one fractional digit ([$ 10.00] → [$ 10.0])

  - Exponent-family values use lowercase [$ e]
  - Exponent-family values remove redundant exponent sign and leading exponent
    zeroes ([$ 1.0E+03] → [$ 1e3])

  - Zero follows the same family model rather than a special ad hoc rule:
    - integer zero → [$ 0] or [$ -0]
    - decimal zero → [$ 0.0] or [$ -0.0]
    - exponent zero → [$ 0e0] or [$ -0e0]

  ~~~$ aeon
  // Non-canonical → Canonical
  1_000_000   → 1000000
  1.2300      → 1.23
  .10         → 0.10
  .5          → 0.5
  1.0E+03     → 1e3
  10.00       → 10.0
  0.0e+0      → 0e0
  ~~~$

  ### Radix [# radix]

  - Canonicalization of [$ :radix\\[...\\]] values remains representation-preserving
    rather than value-normalizing

  - [$ _] separators are removed from the canonical payload
  - the remaining digit sequence, decimal point placement, and leading zero width
    are otherwise preserved

  ~~~$ aeon
  // Non-canonical → Canonical
  %10_00      → %1000
  %10.00      → %10.00
  %0010.00    → %0010.00
  ~~~$

  ### Booleans [# booleans]

  Always [$ true] or [$ false] (lowercase).

  ### Toggle [# toggle]

  Preserve original literal ([$ yes], [$ on], etc.).

  ### Base Literals [# base-literals]

  - Hex: lowercase ([$ #ff00aa])
  - Remove [$ _] separators

  ### Strings [# strings]

  - Always double quotes ([$ "])
  - Minimal escaping
  - Raw line breaks → [$ \\\\n]
  - Non-ASCII preserved as UTF-8

  Multiline semantic strings canonically emit as spaces-only trimticks:

  - canonical output never uses tabs in the trimtick gutter
  - canonical equality is determined by the resulting trimmed string value
  - single-line strings continue to emit as ordinary quoted strings
  - trimticks may collapse to ordinary quoted strings in inline canonical contexts
  - one-line normalized trimticks in inline containers emit as ordinary quoted strings
  - multiline trimticks rendered inside inline object or attribute forms emit as escaped quoted strings rather than multiline trimtick blocks

  ### Separator Literals [# separator-literals]

  Canonical separator literals:

  - No whitespace between [$ =] and [$ ^]
  - No raw whitespace outside quoted segments
  - Raw segments are emitted verbatim
  - Quoted segments use canonical quoted-string escaping

  ~~~$ aeon
  // Non-canonical → Canonical
  size:sep["x"] =^300x250   → size:sep["x"] = ^300x250
  ~~~$

  Quoted segments preserve their string content:

  ~~~$ aeon
  data:sep["|"] = ^"one "|" two"   // preserved as-is
  ~~~$


  ## References [# references]

  References MUST preserve semantic intent:

  - clone references remain clone references ([$ ~...])
  - pointer references remain pointer references ([$ ~>...])

  Canonical form MUST NOT:

  - Change a clone-intent reference into a pointer-intent reference or vice versa
  - Inline or resolve references
  - Alter the logical value graph

  Canonical reference rendering also applies these normalizations:

  - explicit root prefixes are elided when redundant ([$ ~$.a] → [$ ~a], [$ ~>$.a] → [$ ~>a])
  - quoted member or attribute selectors may collapse to bare identifier form when the decoded segment is already a canonical bare identifier ([$ ~a.@.\\["meta"\\]] [>] [$ ~a.@.meta])

  ## Node Heads [# node-heads]

  Canonical node-head ordering is:

  - [$ tag@{...}:datatype]

  Canonical form preserves this order for node heads in the same way it preserves [$ key@{...}:type] ordering for bindings.


  ## Whitespace [# whitespace]

  - 2-space indentation (no tabs)
  - One space around [$ =]
  - LF line endings ([$ \\\\n])
  - Opening brace on same line as binding
  - Closing brace on own line

  ~~~$ aeon
  user = {
    name = "Patrik"
    age = 49
  }
  ~~~$


  ## Non-Goals [# non-goals]

  Canonical form does NOT:

  - Alter the logical value graph
  - Inline references
  - Change types
  - Add or remove bindings (except default header)
`
