Found while closing #89.
EndpointClientCoverageTests exists to enforce the CLAUDE.md rule "Jeder neue API-Endpoint braucht beide Clients" at the level of endpoint existence. It did not catch #89 — the effective-sizing endpoint shipping without a CLI client — and it reported the surface as covered.
Cause
IsCovered treats a wildcard on either side as compatible:
if (e[i] != c[i] && e[i] != "*" && c[i] != "*") { allCompatible = false; break; }
Both sides normalize parameterized segments to *. The CLI's GetSettingsSectionAsync requests api/admin/settings/{section} → api/admin/settings/*, which then matches the literal endpoint route api/admin/settings/effective-sizing. Every literal sub-route under a path where a client has a parameterized sibling is silently "covered".
This is not specific to admin-settings: it applies wherever a client has a by-id/by-name/by-section call next to literal sibling routes.
Measured blast radius
Tightening to "a client * only satisfies an endpoint *":
if (e[i] != c[i] && !(e[i] == "*" && c[i] == "*")) { allCompatible = false; break; }
surfaces 7 additional routes:
api/alerting/catalog
api/alerting/deliveries
api/alerting/preview-filter
api/alerting/preview-rule
api/workflows/*/move-folder
api/workflows/by-name/*
api/workflows/export
These are a mix: some are genuine gaps, but api/alerting/deliveries, api/workflows/by-name/* and api/workflows/export do have CLI call sites, so the strict rule also produces false positives that need per-route investigation (likely a normalization mismatch on the client-scan side rather than a real gap).
Deliberately left out of #89's PR — fixing it properly means auditing each of those 7 and either closing the gap or adding an honest known-gaps entry, which is well beyond that PR's scope.
Suggested work
- Tighten
IsCovered so a client wildcard cannot satisfy an endpoint literal.
- Investigate why the three routes with real call sites do not match — probably the client URL normalization, not the matcher.
- Close or document the remainder.
- Add a test for the matcher itself: a client route
api/x/* must not cover endpoint api/x/literal. Without it this regresses invisibly again.
Found while closing #89.
EndpointClientCoverageTestsexists to enforce the CLAUDE.md rule "Jeder neue API-Endpoint braucht beide Clients" at the level of endpoint existence. It did not catch #89 — theeffective-sizingendpoint shipping without a CLI client — and it reported the surface as covered.Cause
IsCoveredtreats a wildcard on either side as compatible:Both sides normalize parameterized segments to
*. The CLI'sGetSettingsSectionAsyncrequestsapi/admin/settings/{section}→api/admin/settings/*, which then matches the literal endpoint routeapi/admin/settings/effective-sizing. Every literal sub-route under a path where a client has a parameterized sibling is silently "covered".This is not specific to admin-settings: it applies wherever a client has a by-id/by-name/by-section call next to literal sibling routes.
Measured blast radius
Tightening to "a client
*only satisfies an endpoint*":surfaces 7 additional routes:
These are a mix: some are genuine gaps, but
api/alerting/deliveries,api/workflows/by-name/*andapi/workflows/exportdo have CLI call sites, so the strict rule also produces false positives that need per-route investigation (likely a normalization mismatch on the client-scan side rather than a real gap).Deliberately left out of #89's PR — fixing it properly means auditing each of those 7 and either closing the gap or adding an honest known-gaps entry, which is well beyond that PR's scope.
Suggested work
IsCoveredso a client wildcard cannot satisfy an endpoint literal.api/x/*must not cover endpointapi/x/literal. Without it this regresses invisibly again.