Skip to content

Migration Guide: Flude v1.0 to v2.0

NOTE

Archive: historical migration document. V2.0 shipped; this guide's audience is now narrowly "I still have a v1.0 setup to move," not "how does V2.0 work." Everything described below is also covered, as current fact rather than migration delta, in the main sections listed on the Documentation Map — start there for anything that isn't specifically about moving off v1.0.

This guide is the complete checklist for moving a working v1.0 setup to v2.0. Every command, field name, error message and output sample below was verified against the shipped engine on 2026-08-02 — where v2.0 raises a hard error on a v1.0 input, the exact message is quoted so you can match it against your logs. Two passages below were corrected on 2026-08-14 after a follow-up code audit found they no longer matched the shipped engine (the [groups] schema, and one quoted exception message) — see Sidebar & Groups for the current, authoritative version of that material.

Read the Hard stop sections first: they are the changes that make a previously working configuration fail outright.

Hard stop 1: every document directory now requires sidebar.toml

This is the single most likely reason a v1.0 project stops building on v2.0.

sidebar.toml replaced the per-renderer toc_*.json files as the source of navigation truth. Delete any toc_*.json you carried over — they are no longer read by anything.

The file is mandatory and must declare at least one [[sidebar]] entry. The pipeline refuses a document directory otherwise rather than falling back to defaults, because navigation that renders successfully but wrongly is far harder to notice than a failed build. Three separate failures, all fatal:

text
Required sidebar.toml not found in <dir>. Every document directory must contain a sidebar.toml file.
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default navigation to fall back on ...
Failed to parse required sidebar.toml '<path>': <parser detail>

Place one in each <project>/ directory that has a ude_doc_config.json, including the Hugo variants. A minimal file is three lines:

toml
[[sidebar]]
type = "api_reference"
label = "API Reference (C++)"
url = "index.html"

Configuration for most other keys (src_dir, renderer.type, output_subdir, ...) resolves through a three-tier deep merge (global → SDK → document), so shared values live at the outer tiers and each document directory carries only its overrides. [[sidebar]] and [groups] are the exception: neither participates in that JSON cascade at all — see below.

The folder taxonomy that used to live in template JSON now sits in a [groups] table, inside the same mandatory sidebar.toml:

toml
[groups]
namespace_level = ["Classes", "Structures", "Enums"]

namespace_level is the only key GroupsConfig permits (extra="forbid", engine/ude/config.py:56-68) — there is no class_level. A misspelled key (or a stray class_level copied from an old example) is a hard error, not a silently-ignored one.

There is no cascade and no engine-tier default for [groups], and none for [[sidebar]] either — the former per-Lang×Output default TOML fixtures were removed outright in [IMP-32.10]. sidebar.toml's own [groups] is the sole source; groups/sidebar keys are actively rejected if they appear in ude_global_config.json, ude_sdk_config.json, or ude_doc_config.json (see Sidebar & Groups).

[[sidebar]] and [groups] behave identically, deliberately — no asymmetry: both are mandatory, and both fail the build if empty or omitted (engine/ude/orchestrator.py:339, :352):

TableIf you omit it
[[sidebar]]Build fails. There is no default navigation at any tier.
[groups]Build fails. There is no default taxonomy at any tier — every document must declare at least one namespace_level entry.

An earlier version of this guide described [groups] as falling back to an engine default when omitted. That engine-tier default was removed before v2.0 shipped; both tables are equally mandatory today. So an empty sidebar.toml is not valid — it fails exactly like a missing one:

text
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default
navigation to fall back on — every document directory must define its own
[[sidebar]] table.

An explicit sidebar = [] is rejected for the same reason.

Hard stop 2: removed and renamed configuration keys

sidebar_structures_dir was removed from ude_global_config.json. It is trapped explicitly rather than ignored, so a stale key fails loudly:

text
sidebar_structures_dir is removed; folder taxonomy is now sourced exclusively from sidebar.toml [groups].

Remove the key and move its content into sidebar.toml's [groups] table.

renderer.type must be a renderer family key, not a renderer class name. Configs naming a concrete class (CppHtmlODARenderer, PyHugoDefaultRenderer, …) fail with:

text
Unsupported format '<value>' in target config.

The renderer's own factory selects the language-specific subclass from collector.language, so the family key is all you supply:

renderer.typeOutput
html, static_htmlStandalone static HTML
oda_htmlODA-conventions HTML
hugo_markdown, markdown, hugoHugo Markdown
oda_hugo_markdown, oda_markdown, oda_hugoODA-conventions Hugo Markdown

Unknown keys elsewhere in ude_global_config.json are still ignored silently (extra="ignore"), and every field has a default — a config missing a field will not raise.

Typed entity models

The untyped ClassEntity has been replaced by strict Pydantic models. The change you are most likely to feel is in class fields:

  • v1.0: fields: ["int count", "string name"]
  • v2.0: fields: List[VariableModel], each with .name, .type and .docstring

If you maintain a custom renderer or any code that reads the IR, replace string parsing with attribute access. Seven typed models ship in total, covering variables, constants, enums, type aliases, parameters, overloads and methods.

Existing imports keep working. ClassEntity, NamespaceEntity and MethodEntity remain as module-level aliases of the new model classes and are explicitly maintained for external consumers — you do not have to rename imports as part of this migration.

ProjectCatalog metadata

ProjectCatalog gained project_name and version. Both are optional, defaulting to an empty string — no action is required, and neither belongs in ude_global_config.json. They are IR-level metadata populated from your document config; set project_name in ude_doc_config.json if you want it carried into the IR.

Decoupled CLI

The v1.0 flat interface is unchanged and still supported — ude --doc-config … behaves exactly as before, and ude compile is its explicit equivalent. v2.0 adds subcommands that split parsing from rendering, which is what you want if you plan to cache or archive the IR between CI stages.

Note that the IR is gzip-compressed; use a .json.gz extension.

Generate IR:

bash
ude parse --doc-config path/to/ude_doc_config.json --output-ir catalog.json.gz

Render from IR:

bash
ude render --input-ir catalog.json.gz --output ./public --format html

--output-ir is required for parse; --input-ir and --output are required for render. Both accept --global-config / -g and --doc-config / -d.

Coverage auditing and ude audit

Two new GlobalConfig fields drive the documentation coverage gate:

  • coverage_modeallow-undocumented (default, reports only) or reject-undocumented (fails the build).
  • coverage_threshold — a fraction between 0.0 and 1.0, default 1.0.

Unit mismatch, worth pinning to memory. The config field is a fraction (0.98), while the CLI flag is a human-friendly percentage (--threshold 98, valid range 0–100). Passing --threshold 0.98 is accepted but means 0.98 percent, which will pass almost anything. The CLI divides by 100 before handing the value to the gate.

--mode and --threshold override the config values when given explicitly.

Exit codes

CodeMeaning
0Audit completed; gate passed, or mode is allow-undocumented
2Gate rejected the target: reject-undocumented and coverage below threshold

Actual output format

ude audit writes a Markdown report to stdout: a summary header followed by one row per entity — not per module.

markdown
# Documentation Coverage Audit

**Total Entities:** 4948
**Documented Entities:** 969
**Overall Coverage:** 19.58%

| Type | Entity Name | Documented |
| :--- | :--- | :--- |
| Class | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock` | PASS |
| Method | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock.Format` | FAIL |

On a large SDK this is thousands of rows — redirect it to a file rather than into a CI log, and use the exit code for the pass/fail decision.

GitHub Actions integration

yaml
      - name: Flude Coverage Audit
        shell: bash -euo pipefail {0}
        env:
          PYTHONPATH: engine
        run: |
          python -m ude.cli audit \
            --doc-config path/to/ude_doc_config.json \
            --threshold 98 \
            --mode reject-undocumented

When embedding the engine as a library instead, apply_coverage_gate() raises UdeException rather than calling sys.exit(), so a host application is never killed by a coverage failure.

Also new in v2.0 (no migration action required)

These arrive automatically and need no configuration change:

  • Unified logging — one ude root logger driven by log_level / log_file. Importing ude.config without calling logging_setup() produces no output, per the standard library contract, so embedding the engine stays quiet by default.
  • L2 render cache — unchanged entities skip re-rendering on repeat builds.
  • Three-tier Doxyfile merge — key-level merge across global, SDK and document tiers, resolved via global_templates_dir.
  • Public library APIUdeOrchestrator exposes parse, render and run for embedding without going through the CLI.

Step-by-step migration checklist

  1. [ ] Add a sidebar.toml declaring at least one [[sidebar]] entry to every directory containing a ude_doc_config.json, Hugo variants included. The build fails on a missing, empty or malformed file — there is no default navigation.
  2. [ ] Delete leftover toc_*.json files; move any folder taxonomy into sidebar.toml's [groups] table.
  3. [ ] Remove sidebar_structures_dir from ude_global_config.json if present.
  4. [ ] Change any renderer.type naming a concrete renderer class to the corresponding family key from the table above.
  5. [ ] Update custom renderers and IR consumers to read VariableModel attributes (.name, .type, .docstring) instead of parsing strings. Legacy ClassEntity / NamespaceEntity / MethodEntity imports need no change.
  6. [ ] Optional: adopt ude parse / ude render if you want the IR as a separate CI artifact. ude compile and the v1.0 flat flags keep working.
  7. [ ] Optional: set coverage_mode and coverage_threshold, and add ude audit to CI — remembering the fraction-vs-percentage distinction.

Verifying the migration

bash
# 1. A v1.0 config still compiles through the flat interface
ude --doc-config path/to/ude_doc_config.json

# 2. A previously saved v1.0 IR still loads
python -c "from ude.storage import load_compressed_ir; load_compressed_ir('old_catalog.json.gz')"

# 3. Split pipeline produces the same output as a single-pass compile
ude parse  --doc-config path/to/ude_doc_config.json --output-ir /tmp/ir.json.gz
ude render --input-ir /tmp/ir.json.gz --output /tmp/split --format html --doc-config path/to/ude_doc_config.json
ude compile --doc-config path/to/ude_doc_config.json --output /tmp/single --format html

# diff -rq /tmp/split /tmp/single should report no difference except
# .build_cache.json.gz (internal L2 cache metadata, not rendered output).

--doc-config on the render line is not optional, despite --input-ir alone being enough to produce some output. Without it, render has no sidebar.toml/[groups] context to resolve navigation from and falls back to a bare, unlabeled api_reference entry — different folder grouping, different sidebar labels, same entities. Verified 2026-08-10 end-to-end: a real split-vs-compile run with --doc-config omitted from render produced a structurally different nav_data.js (flat class list instead of a grouped "Classes" folder, generic "API Reference" label instead of the project's own); adding the flag back made every output file byte-identical to the single-pass compile.

Related Docs: (Paths below are repository-root-relative.)

  • user-docs/docs/cli-reference.md — full reference for the subcommands introduced here
  • user-docs/docs/changelog.md — condensed v1.0 -> v2.0 release notes
  • user-docs/docs/global-settings.mdude_global_config.json field reference
  • user-docs/docs/target-settings.mdude_doc_config.json field reference