Skip to content

feat(strict): add DEFAULT_MATCHES_FIRST_ALLOWED check for default==allowed[0] - #512

Merged
erikbosch merged 3 commits into
COVESA:masterfrom
aki1770-del:sprint130-t2-default-matches-first-allowed
Apr 30, 2026
Merged

feat(strict): add DEFAULT_MATCHES_FIRST_ALLOWED check for default==allowed[0]#512
erikbosch merged 3 commits into
COVESA:masterfrom
aki1770-del:sprint130-t2-default-matches-first-allowed

Conversation

@aki1770-del

Copy link
Copy Markdown
Contributor

Closes #507.

Per @erikbosch (#507 comment 2026-04-15): "I think check b (i.e. that default value matches first value) at least should be part of the 'strict' check we have". Per @sschleemilch: original proposal (a) "default ∈ allowed" is already enforced in pydantic VSSDataDatatype (see tests/test_model.py#L39), so this PR lands only the narrowed (b) check.

Why this matters

Proto3 nested-enum generation from allowed (landed in #502) uses allowed[0] as the implicit wire-unset default value — proto3 has no mechanism to set a non-zero default for strings or ints. If VSS default does not match allowed[0], the serialized-and-deserialized value silently becomes allowed[0], diverging from the VSS author's declared intent.

The canonical VSS pattern is:

Vehicle.Cabin.Mode:
  datatype: string
  allowed: ['UNKNOWN', 'ECO', 'SPORT']
  default: 'UNKNOWN'

See Powertrain/CombustionEngine.vspec Configuration for an in-tree example (referenced in #507 body).

What this PR does

Adds a new strict-check option, parallel to the existing NAME_STYLE and UNKNOWN_ATTRIBUTE machinery:

  • StrictOption.DEFAULT_MATCHES_FIRST_ALLOWED enum member
  • StrictExceptions.defaults per-FQN exemption set
  • load_strict_exceptions dispatch for the new option (including the null-options fallback that exempts an FQN from all strict checks)
  • VSSNode.get_default_first_allowed_violations() tree walker
  • check_default_first_allowed_violations() enforcement in get_root()
  • DefaultFirstAllowedException signal
  • --aborts CLI choice extended with default-matches-first-allowed

Gated behind --strict or --aborts default-matches-first-allowed. Existing runs without either flag are unchanged — no behavior regression.

Error message

When a violation is detected (under strict/abort mode), the warning is:

default != allowed[0]: 'Vehicle.Cabin.Mode' (default='ECO' != allowed[0]='UNKNOWN').
Canonical pattern: allowed: ['UNKNOWN', ...] + default: 'UNKNOWN'.

Testing

  • 5 new tests (1 in test_strict.py for the new enum + dispatch; 4 in test_vss_node.py for the tree-walk violation collector covering clean / mismatch / no-allowed / no-default branches)
  • Existing test_strict.py tests extended with .defaults set assertions for consistency with .names / .attributes
  • Full suite: 132/132 pass on tests/ --ignore=tests/vspec --ignore=tests/binary (pre-existing test_binary failure verified unrelated — reproduces on clean master)
  • ruff check src/ tests/test_strict.py tests/test_vss_node.py clean

Chain context

Chain A: #502#507. PR #502 adds the proto3 nested-enum exporter that creates the default-vs-allowed divergence risk; this PR adds the strict validation that prevents authors from committing VSS specs with that silent-divergence.

…lowed[0]

Closes COVESA#507.

Follow-up from COVESA#502 (proto3 nested-enum generation for string fields with
allowed values). Proto3 uses allowed[0] as the implicit wire-unset default,
so VSS default must match allowed[0] for the intent to survive
serialization. Canonical VSS pattern per erikbosch + sschleemilch
(see COVESA#507 comments): allowed: ['UNKNOWN', ...] + default: 'UNKNOWN'.

Implementation mirrors existing NAME_STYLE / UNKNOWN_ATTRIBUTE strict
checks:
  - StrictOption.DEFAULT_MATCHES_FIRST_ALLOWED enum member
  - StrictExceptions.defaults exemption set
  - load_strict_exceptions dispatch (+ null-options fallback)
  - VSSNode.get_default_first_allowed_violations tree walk
  - check_default_first_allowed_violations strict gate in main.py
  - DefaultFirstAllowedException signal class
  - aborts CLI choice extended

Gated behind --strict or --aborts default-matches-first-allowed; exempt
per-FQN via the existing --strict-exceptions file (new option value or
null for all-exceptions). No behavior change on default run.

Tests: 5 new (1 strict.py enum + dispatch, 4 tree.py violation collector
covering clean/mismatch/no-allowed/no-default), plus existing strict.py
tests extended with .defaults-set assertions for consistency. 132/132
pass on tests/ (pre-existing test_binary failure unrelated).

Chain A: COVESA#502 -> COVESA#507.

Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…ruff-format

Array datatypes (`string[]` etc.) don't have a proto3 nested-enum analog
for the default-equals-allowed[0] rule — per-element allowed-membership
is already enforced at the pydantic VSSDataDatatype layer. Previous
implementation incorrectly flagged
`tests/vspec/test_datatypes_pattern/test_pattern_ok.vspec` (A.Colors:
string[] with allowed=[white,...,blue] and default=[white,green,red])
as a violation because default (a list) != allowed[0] (a scalar).

Adds early-continue on `node.data.datatype.endswith("[]")` and a
regression test covering the exact fixture shape.

Also applies ruff-format on main.py and cli_options.py to satisfy the
pre-commit ruff-format hook (whitespace-only changes).

Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
@aki1770-del

Copy link
Copy Markdown
Contributor Author

Thanks, Sebastian. Should I land this myself, or are you handling the merge?

@erikbosch

Copy link
Copy Markdown
Collaborator

MoM:

@erikbosch erikbosch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@erikbosch
erikbosch merged commit b5501e2 into COVESA:master Apr 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate default == allowed[0] when field generates a nested enum (proto3)

3 participants