| id | 135 | ||
|---|---|---|---|
| title | Schema inheritance via `extends` | ||
| status | ✅ | ||
| model | sonnet | ||
| depends-on |
|
||
| summary | Let one kind's schema extend another so common front-matter fields and structure templates live in a base. Single-parent inheritance with child-wins semantics, applied to both inline (plan 146) and file schemas. Errors point at the layer that introduced a conflict. |
Let a kind's schema build on a base schema. A
project with three RFC variants (draft, ratified,
deprecated) should declare the shared fields once
in a rfc-base schema and only the differences
in the variants.
The mdbase research records this as S-3.
CUE already supports unification. A proto.md
schema can compose via import plus struct
embedding today. What is missing is an explicit
extends: surface that:
- works in inline schemas (plan 146), where
importis not the user's primary tool, - shows up in
mdsmith kindsso the inheritance chain is visible without reading every schema, - gives an actionable error (plan 147) when a child conflicts with its parent.
- Multiple inheritance. Single parent only. CUE
supports multi-parent unification, but the
debugging cost is real and the use cases are
thin. Composition via
<?include?>covers the remaining case. - Diamond resolution rules. Single inheritance makes them moot.
- Schema versioning. A separate concern, tracked as V-1 in the research.
kinds:
rfc-base:
schema:
frontmatter:
id: '=~"^RFC-[0-9]{4}$"'
authors: '[...string] & len(authors) >= 1'
created: date
structure:
- "# {title}"
- "## Context"
- "## Decision"
rfc-ratified:
extends: rfc-base
schema:
frontmatter:
ratified-on: date
status: '"ratified"'Two layers of effect:
- The schema engine refines the parent under
CUE unification: the child's expression for a
shared key is unified with the parent's, and
the child must therefore satisfy the parent's
constraint. A child that cannot unify with
the parent (parent says
int, child saysstring) is a conflict; a child that narrows the parent (parent says"open" | "closed", child says"open") is a valid refinement. - The kind's rule overrides also inherit (the
same deep-merge plan 97 applies).
extends:here is the schema-side surface; the rule-side surface remainskinds:order plus deep-merge.
A file schema declares its parent in front matter:
---
extends: rfc-base.proto.md
---
# {title}
...The path is resolved relative to the schema file. The loader recurses; cycles are detected and reported with the cycle path.
frontmatter: keys unify under CUE's standard
rules. The effective expression is the unified
form; the child must satisfy the parent.
# parent: status: '"open" | "closed"'
# child: status: '"open"'
# effective: status: '"open"' (refinement; OK)
# parent: status: '"open" | "closed"'
# child: status: '"ratified"'
# effective: conflict (no value satisfies both)structure: does not unify — heading
templates compose by sequence, not by
constraint, so the child's template replaces
the parent's wholesale. To extend a parent's
template, copy the parent's lines and add to
them. A future plan can revisit if real cases
need finer-grained merge.
mdsmith kinds show <name> prints:
rfc-ratified:
extends: rfc-base
schema: inline
effective-frontmatter:
id: =~"^RFC-[0-9]{4}$" # from rfc-base
authors: '[...string]' # from rfc-base
created: types.#date # from rfc-base
ratified-on: types.#date # from rfc-ratified
status: '"ratified"' # from rfc-ratified
The provenance column lets a reader see which
layer contributes each field without reading all
schemas. This is the extends: analogue of plan
97's deep-merge provenance for rule settings.
A child's CUE expression may be unsatisfiable
against its parent's. Parent says int; child
says string. The diagnostic from plan 147
names both layers:
status: schema cannot unify with parent
parent rfc-base: '"open" | "closed"'
child rfc-ratified: 'int'
schema: kinds[rfc-ratified] / extends[rfc-base]
The reader sees the field, both expressions, and both layer names without grepping.
- Add an
Extends stringfield to the kind config struct ininternal/config/. - Add an
extends:front-matter field to theproto.mdschema parser ininternal/rules/requiredstructure/. - Implement parent resolution:
- inline kinds resolve
extends:against thekinds:map by name, - file schemas resolve against a path relative to the schema file.
- Detect cycles and report with the full cycle
path (
a → b → c → a). Reject before any evaluation. - Unify child schema onto parent for both
frontmatter:andrequire:. Replace wholesale forstructure:. Reject conflicts with the diagnostic shape above (depends on plan 147). - Extend
mdsmith kinds show <name>to print the inheritance chain and per-field provenance forfrontmatter:. - Document inheritance in the file-kinds guide with a worked RFC example.
- Tests:
- inline + file schemas each accept
extends:and resolve correctly, - a child overrides a parent field; the effective schema reflects the child,
structure:replacement (not merge) is the observed behavior, with a regression test,- cycle detection produces the expected error before any unification runs,
- conflict diagnostics name both layers.
- An inline kind with
extends:inherits its parent'sfrontmatter:keys; the child can override individual keys. - A file schema with
extends: <path>inherits identically. - A child's
structure:wholly replaces the parent'sstructure:(regression test asserts the parent's headings are absent from the effective schema). - A cycle in
extends:(single or multi-hop) produces an error naming the cycle path. - A child whose CUE expression cannot unify with its parent's produces a diagnostic naming both layers (depends on plan 147).
-
mdsmith kinds show <name>prints the inheritance chain and per-field provenance. - All tests pass:
go test ./... -
go tool golangci-lint runreports no issues.