Context
Follow-up from PR #360 (slice #354 — multi-class launch()).
_build_entry_model builds each entry's type field as Literal[discriminator] with a default value equal to the discriminator:
# src/fastcs/launch.py:217
fields["type"] = (Literal[discriminator], discriminator)
The default is what lets single-class configs omit type: from fastcs.yaml. But:
- In single-class mode,
_build_options_model uses the entry model directly. The type: default works — Pydantic falls through to it on omission.
- In multi-class mode,
_build_options_model wraps the entry models in Annotated[Union[...], Field(discriminator=\"type\")]. Pydantic's discriminated-union resolution does not consult field defaults to pick a branch; an entry without type: raises a discrimination error.
So the same line of code produces "optional" behaviour in one mode and "required" behaviour in the other. The asymmetry is small but the kind of thing that silently shifts the day someone registers a second class.
Options
- Status quo + comment: leave the asymmetry, add a one-line code comment at the
fields[\"type\"] site noting "default takes effect only in single-class mode."
- Require
type: always: drop the default. Single-class users gain a redundant type: MyController line in YAML; in exchange the rule is one rule (type: is mandatory) and the simple case looks the same as the multi-class case.
The user-facing trade-off is "redundant line in trivial configs" vs "one consistent rule and no mode-dependent behaviour."
What to decide
Pick option 1 or 2 and apply.
Acceptance criteria
Context
Follow-up from PR #360 (slice #354 — multi-class
launch())._build_entry_modelbuilds each entry'stypefield asLiteral[discriminator]with a default value equal to the discriminator:The default is what lets single-class configs omit
type:fromfastcs.yaml. But:_build_options_modeluses the entry model directly. Thetype:default works — Pydantic falls through to it on omission._build_options_modelwraps the entry models inAnnotated[Union[...], Field(discriminator=\"type\")]. Pydantic's discriminated-union resolution does not consult field defaults to pick a branch; an entry withouttype:raises a discrimination error.So the same line of code produces "optional" behaviour in one mode and "required" behaviour in the other. The asymmetry is small but the kind of thing that silently shifts the day someone registers a second class.
Options
fields[\"type\"]site noting "default takes effect only in single-class mode."type:always: drop the default. Single-class users gain a redundanttype: MyControllerline in YAML; in exchange the rule is one rule (type:is mandatory) and the simple case looks the same as the multi-class case.The user-facing trade-off is "redundant line in trivial configs" vs "one consistent rule and no mode-dependent behaviour."
What to decide
Pick option 1 or 2 and apply.
Acceptance criteria
controller.yaml,tests/data/config.yaml, snippets, and any docs migrate to includetype: