Acceptance Criteria
- A theme override keyed to a rune whose config came from
composedRuneConfig is reported, naming the theme, the rune and the path, and dropped whole - It is reported through the same sink and once-per-path rule as identity violations
- Native runes' overrides are unaffected: identity fields stay guarded and the rest stays overridable
- A test pins that an override of a composed rune's
blocks is reported and dropped, and that Lumina's config trips nothing - The theme-authoring docs say a composed rune's config cannot be overridden, and point to ADR-042 and to template replacement (ADR-045) as the route a theme has
Approach
assembleThemeConfig already carries provenance for plugin runes (packages/transform/src/assemble.ts). Composed runes need the same marker, so the merge can tell a composed config from a native one without inspecting its shape.
References
- ADR-042: Decision 1, and Implementation.
Resolution
Completed: 2026-10-29
Branch: claude/milestone-v0-44-0-ism7ni
What was done
RuneConfig.composed (provenance marker), set by composedRuneConfig.mergeThemeConfig (packages/transform/src/merge.ts): an override of a composed rune is reported through the identity sink (once per path, runes.<Rune>, naming the theme) and dropped whole; a composed declaration replaces the entry under its key rather than merging over it, and reports a non-composed entry it displaces.mergeThemeConfig takes { themeName }; assembleThemeConfig takes themeName; Lumina passes 'lumina'.- Tests:
packages/transform/test/identity-fields.test.ts, packages/lumina/test/identity-guard.test.ts. - Docs:
site/content/extend/theme-authoring/config-api.md ("Composed runes").
Notes
- The marker is a plain field rather than a symbol so it survives a JSON boundary; the merge never infers it from the config's shape.