Skip to content

Enforce declared capability limits; add name-length limits - #7

Merged
emuehlstein merged 1 commit into
mainfrom
feat/enforce-capability-limits
Aug 10, 2026
Merged

Enforce declared capability limits; add name-length limits#7
emuehlstein merged 1 commit into
mainfrom
feat/enforce-capability-limits

Conversation

@emuehlstein

Copy link
Copy Markdown
Contributor

Closes part of #3 (section 4), and fixes a gap the issue did not name.

The gap

capabilities-0.2 declares nine limit keys. profile.py checks three:
max_zones, max_channels_per_zone, max_channels (lines 214/244/251 on
main). Everything added in aabcdddmax_scan_lists,
max_channels_per_scan_list, max_rx_group_lists,
max_talkgroups_per_rx_group_list — is declared, schema-validated, and
never checked against a profile.

That is the same failure shape #3 was filed about. A limit that validates
clean and never fires is worse than an absent one, because it looks
enforced. #3 frames the problem as "the numbers are wrong"; the numbers are
right as of aabcddd, but most of them are inert.

What this does

Adds the four name-length limits from #3 section 4, and enforces the two
that apply to profile 0.1 today.

  • schema: declare max_channel_name_chars, max_zone_name_chars,
    max_scan_list_name_chars, max_contact_name_chars. limits is
    additionalProperties: false, so these had to be declared or they would
    be rejected.
  • radios/baofeng_dm32: 16 / 16 / 10 / 16, per DM-32 capabilities.json models the CPS ceiling, not the radio's real constraints #3.
  • profile.py: _check_name_length(); zone names checked during
    profile validation.
  • resolved.py: channel display names checked as they resolve. That is
    the first point at which a channel name exists — 0.1 profiles select
    assignments, and names come from SSRF, so this check cannot live in
    profile validation.

Absent limits skip their check, matching the existing
degrade-rather-than-fail behavior of _check_radio_support. An incomplete
capabilities file does not start producing false failures.

Also extracts _load_capabilities() so the resolver reads capabilities
through the same schema-validated path as the validator, instead of
re-reading the file.

Naming note

Used max_scan_list_name_chars, not #3's max_scanlist_name_chars
consistent with the existing max_scan_lists /
max_channels_per_scan_list. Easy to change if you would rather match the
issue text.

Still not enforced, deliberately

max_scan_list_name_chars and max_contact_name_chars are declared but
unchecked: profile 0.1 has no scan lists or contacts to check them
against. Recorded now so 0.2 designs against them rather than
rediscovering them on hardware — which is #3's own argument for filing
early.

The scan-list count limits stay inert for the same reason. Worth a
follow-up issue so they do not sit there looking enforced.

Verification

8 passed    <- before
13 passed   <- after

New coverage: over-limit rejection, at-limit acceptance (16 chars must
pass, not fail), absent-limit skip, and rejection via the resolver path.

Confirmed the new tests actually exercise the change — stashed the diff and
re-ran: 8 pass on aabcddd, so the 5 new tests genuinely fail without it
rather than passing vacuously.

Not hardware-tested. These are storage-derived limits from #3's cited
sources; nothing here has been written to a radio.

capabilities-0.2 declared several limits that nothing ever checked. A
profile could validate clean while exceeding them, which is the failure
mode issue #3 was filed about: a limit that never fires is worse than an
absent one, because it looks enforced.

Adds the four name-length limits from #3 section 4 and wires up the two
that are enforceable against profile 0.1 today:

- schema: declare max_channel_name_chars, max_zone_name_chars,
  max_scan_list_name_chars, max_contact_name_chars. The limits block is
  additionalProperties: false, so undeclared keys are rejected outright.
- baofeng_dm32: 16/16/10/16, per the values in #3.
- profile.py: _check_name_length() helper; zone names checked during
  profile validation.
- resolved.py: channel display names checked as they resolve, which is
  the first point where a channel name exists (0.1 profiles select
  assignments; names come from SSRF).

Absent limits skip their check, matching the existing degrade-rather-than-
fail behavior of _check_radio_support, so an incomplete capabilities file
does not start producing false failures.

Scan-list and contact name limits are declared but not yet enforced;
profile 0.1 has no scan lists or contacts to check them against. They are
recorded now so 0.2 designs against them instead of rediscovering them on
hardware.

Also extracts _load_capabilities() so the resolver reads capabilities
through the same schema-validated path as the validator rather than
re-reading the file.

Tests: 8 -> 13. Includes an at-the-limit accept, an absent-limit skip,
and a resolver-path rejection. Verified the new tests fail against the
prior commit.
@emuehlstein
emuehlstein merged commit 7d89e1c into main Aug 10, 2026
2 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.

1 participant