···134134135135Without `--root`, it defaults to the `source.directory` from mlf.toml or the current directory.
136136137137+### `@const` Float Values
138138+139139+ATProto's data model has no floats — integers only. Annotations themselves accept any numeric literal (they're free-form metadata), but `@const` is special: its values become actual fields in the generated JSON Lexicon.
140140+141141+To keep output spec-compliant, the lexicon generator applies two rules to `@const` numeric values:
142142+143143+- **Whole-number floats → integers.** `@const("revision", 3)` emits `"revision": 3` (JSON number), never `3.0`.
144144+- **Fractional floats → strings.** `@const("x-threshold", 3.14)` emits `"x-threshold": "3.14"` (JSON string) and prints a warning. Wrapping as a string is the spec-compliant way to carry non-integer numeric values through ATProto.
145145+146146+If you genuinely need a float-shaped value in your lexicon JSON, pass it as a string in MLF — `@const("x-threshold", "3.14")` — to skip the coercion.
147147+137148---
138149139150## Generate Code
···376387 createdAt!: Datetime,
377388}
378389```
390390+391391+### Conversion Warnings
392392+393393+Some published lexicons in the wild use shapes the ATProto spec doesn't strictly require, or def types MLF doesn't have dedicated syntax for. The converter accepts these with a warning rather than failing, so you can always roundtrip a lexicon and decide what to do next.
394394+395395+**Lenient handling of non-spec shapes:**
396396+397397+| Source JSON shape | Converter behavior |
398398+|---|---|
399399+| `object` with no `properties` field | Treated as an empty object; emits `{}` |
400400+| Open `union` with empty `refs: []` | Emits `unknown` |
401401+| `array` with no `items` field | Emits `unknown[]` |
402402+403403+Each of these produces a warning printed to stderr identifying the namespace and the shape that was coerced.
404404+405405+**Unknown def types:**
406406+407407+When a def's `type` isn't in MLF's known set (`record`, `query`, `procedure`, `subscription`, `object`, `array`, `union`, `ref`, `token`, primitive types), the converter emits it as a placeholder:
408408+409409+```mlf
410410+@const("type", "permission-set")
411411+@const("permissions", [...])
412412+def type mySet = unknown;
413413+```
414414+415415+Every field of the original JSON def is preserved as `@const` annotations. On the return trip through `mlf generate lexicon`, codegen re-emits all those fields verbatim, making any future spec-defined def type roundtrip automatically without grammar work.
416416+417417+**NSID-shaped `@const` strings:**
418418+419419+When a `@const` string value contains `#` (the ATProto local-ref marker), the converter emits a warning suggesting you may want `@reference` instead when hand-editing the MLF. `@reference` resolves type paths through the workspace and emits the resulting NSID at codegen time, which is useful when the target moves; `@const` is kept as the default since it's always safe.
379420380421---
381422