|
| 1 | +# ADR-0006 — AI-first YAML authoring (sigil-free YAML; JSON stays canonical) |
| 2 | + |
| 3 | +**Status:** Proposed (2026-05-23) |
| 4 | + |
| 5 | +**Related:** ADR-0004 (per-subtype attr schemas), ADR-0007 (source v2), the enum datatype design |
| 6 | +(`docs/superpowers/specs/2026-05-23-enum-datatype-design.md`, which defers its YAML spelling here). |
| 7 | + |
| 8 | +## Context |
| 9 | + |
| 10 | +MetaObjects has an optional **YAML authoring front-end** that lowers to canonical JSON metadata |
| 11 | +(`parser-yaml.ts` → `yaml-desugar.ts` → the shared `buildTree`). **Canonical JSON is the |
| 12 | +cross-language interchange + conformance form** and is *unchanged* by this ADR: reserved |
| 13 | +structural keys are bare; inline attributes are `@`-prefixed. |
| 14 | + |
| 15 | +Configs are **AI-first** — Claude generates this metadata — so the authoring format must minimize |
| 16 | +silent footguns and "multiple ways to say the same thing." Four problems with YAML today: |
| 17 | + |
| 18 | +1. **`@` is a YAML reserved indicator.** `@dbColumn:` is a hard parse failure; you must write |
| 19 | + `"@dbColumn":`. A frequent AI mistake. |
| 20 | +2. **YAML 1.2 core-schema coercion.** Unquoted `true/false`, `null/~`, and numeric tokens coerce |
| 21 | + to bool/null/number; the original text is unrecoverable after parse — dangerous for domain data |
| 22 | + (enum members, codes, names). |
| 23 | +3. **Too many equivalent forms** (bare-type defaults, scalar-vs-map body, scalar-vs-array values) |
| 24 | + raise AI error rate. |
| 25 | +4. **YAML is not conformance-tested**, and only the TS `FileMetaDataLoader` loads it. |
| 26 | + |
| 27 | +## Decision |
| 28 | + |
| 29 | +- **D1 — Sigil-free YAML authoring.** A **closed set of structural keys** (`name`, `package`, |
| 30 | + `extends`, `abstract`, `overlay`, `isArray`, `children`, `value`, plus the single |
| 31 | + `type.subType` wrapper). **Any other body key is an attribute**; the desugar re-adds the `@` |
| 32 | + when lowering to canonical JSON. **Canonical JSON is unchanged** — it keeps reserved-bare + |
| 33 | + `@`-attributes. This removes the `@`-quoting footgun entirely. *(Corollary for the JSON path: |
| 34 | + in canonical JSON, `@`-prefixing a reserved word — e.g. `@isArray` — is invalid → |
| 35 | + `ERR_RESERVED_ATTR`. Mostly a hand-edited-JSON guard; humans author YAML.)* |
| 36 | +- **D2 — Type-coercion guard.** (a) House rule: quote domain-data string scalars. (b) |
| 37 | + Deterministic backstop: when the schema declares a value's type, the loader **rejects** a value |
| 38 | + that parsed as a different JS type (bool/number/null) with a clear, located "quote this value" |
| 39 | + error. Never silently accept a coerced value. (Schema-guided parsing where feasible.) |
| 40 | +- **D3 — Reduce degrees of freedom.** Define the single AI **house style** Claude always emits: |
| 41 | + explicit `type.subType`; one consistent body form; quoted string values. The desugar may still |
| 42 | + *accept* human shorthand; optionally lint-warn toward house style. |
| 43 | +- **D4 — First-class + conformance-tested, TS-only scope.** YAML stays a **TS-only authoring |
| 44 | + front-end**; **canonical JSON remains the sole cross-language interchange** (only TS has a YAML |
| 45 | + loader). Add YAML conformance fixtures exercising sigil-free attributes + the coercion guard. |
| 46 | + |
| 47 | +## Consequences |
| 48 | + |
| 49 | +- Changes are **TS-only** (`core/parser-yaml.ts`, `core/yaml-desugar.ts`, the schema-validation |
| 50 | + pass for D2, constants, tests + new YAML fixtures). **Canonical JSON, the other ports, and the |
| 51 | + conformance oracle are unchanged.** |
| 52 | +- **Enum coordination:** `field.enum`'s `values:` (YAML) / `@values` (canonical JSON) follows D1. |
| 53 | +- **Sequencing:** built **last** in the source-v2 rollout — YAML authoring desugars to the |
| 54 | + canonical vocabulary, so source v2 + the persistence attrs must be final first. |
| 55 | +- Low blast radius: YAML is optional + currently unexercised; the canonical form doesn't move. |
| 56 | + |
| 57 | +## Alternatives considered |
| 58 | + |
| 59 | +- **Purge `@` from canonical JSON too** (bare everywhere). Maximally uniform, but migrates 4 |
| 60 | + serializers + every `expected.json` for a cosmetic win, and removes the explicit attribute |
| 61 | + marker from the wire/oracle form. The canonical form staying stable + marked is worth more. |
| 62 | + Rejected — sigil-free *authoring* (YAML) achieves the AI-ergonomics goal without touching the |
| 63 | + canonical contract. |
| 64 | +- **Keep `@` in YAML (quoted).** The footgun being removed. Rejected. |
| 65 | +- **`_`-prefix on reserved words.** YAML-safe but marks the *frequent* set (`name`/`children`) |
| 66 | + and uglifies the canonical form. Rejected. |
| 67 | + |
| 68 | +## Realization status |
| 69 | + |
| 70 | +- **Spec:** `spec/wire-format.md` (canonical key model, unchanged) + this ADR (YAML layer). |
| 71 | +- **TypeScript:** _pending_ — `parser-yaml`/`yaml-desugar`/validation + YAML fixtures. **Other |
| 72 | + ports: out of scope** (canonical JSON is the cross-language interchange). |
0 commit comments