Skip to content

CLI KNOWN_* value lists are hand-maintained copies of spec enums with no drift guard (--region europe-west2 rejected) #291

Description

@sdairs

Follow-up to #275 / #290, which added enum-value drift detection for models.rs. The CLI has the same class of problem one layer up.

Problem

The CLI keeps eight hand-maintained copies of spec enum values for client-side validation (necessary because the library enums' untagged Unknown(String) catch-all means serde never fails):

  • commands.rs: KNOWN_PROVIDERS, KNOWN_REGIONS, KNOWN_RELEASE_CHANNELS, KNOWN_COMPLIANCE_TYPES, KNOWN_PROFILES
  • postgres.rs: KNOWN_PG_PROVIDERS, KNOWN_PG_VERSIONS, KNOWN_PG_HA_TYPES (the latter two also feed clap PossibleValuesParser, i.e. --help output)

Nothing guards any of them — the same silent-rot mode #275 fixed for models.rs. It is already happening: KNOWN_REGIONS is missing europe-west2 (added to the library in #290) and ca-central-1 (added in #298, i.e. the list rotted again while this issue was open). parse_serde_enum hard-rejects unknown values, so chctl cloud service create --region europe-west2 fails client-side even though the API accepts it.

We will not patch the lists by hand — that just resets the clock. Fix the mechanism instead.

Fix

Since #290 the library enums are guaranteed in lockstep with the spec, so derive the CLI's value lists from the library and have the analyzer enforce the new surface:

1. Library: pub const VALUES on the value enums the CLI consumes

In models.rs, add to each enum the CLI validates against (ServicePostRequestProvider, ServicePostRequestRegion, ServicePostRequestReleasechannel, ServicePostRequestCompliancetype, ServicePostRequestProfile, PgProvider, PgVersion, PgHaType):

impl ServicePostRequestRegion {
    /// Wire values accepted by the API, excluding the catch-all.
    pub const VALUES: &'static [&'static str] = &["ap-northeast-1", /* … */];
}

Hand-written literal slices, not a proc macro or strum: wire values come from #[serde(rename)], not variant names, and this repo's approach (explicit serde literals, rename_all rejected) depends on greppable literals syn can parse. A macro-generated const would be invisible to the analyzer.

2. Analyzer: verify VALUES against the enum's wire values

VALUES is another copy of the literals — acceptable only because it is machine-checked. The analyzer already parses every enum's variants, serde renames, and catch-alls with syn. Add a typed FindingKind: any enum that declares a VALUES const must have it exactly equal to its non-catch-all wire values. Opt-in by construction ("if a VALUES const exists, it must match"), so the remaining enums need no change. Per the extension policy: focused inventory/comparison fixtures, deterministic JSON/text coverage, report schema_version bump, Python issue rendering; spec_coverage_test.rs stays a thin consumer.

This closes the chain: spec ↔ enum variants (guarded since #290) and enum variants ↔ VALUES (new check), so VALUES is transitively spec-accurate.

3. CLI: delete the KNOWN_* constants

  • parse_serde_enum::<ServicePostRequestRegion>(value, "region", ServicePostRequestRegion::VALUES) — error messages ("expected one of …") now sourced from the library.
  • Clap gets the same source: PossibleValuesParser::new(PgVersion::VALUES), so --help for the Postgres flags updates automatically.

Populating the new VALUES consts from the current enums fixes the europe-west2 / ca-central-1 gaps as a side effect.

Result

A spec drift remediation that adds an enum value flows automatically: spec → analyzer finding → models.rs variant + VALUES (analyzer fails CI if VALUES is not updated) → CLI validation, error messages, and --help update with zero CLI edits.

Out of scope / already fine

  • pg create --size deliberately takes a free-form String ("Server validates — accepts any value"), so PgSize needs no CLI change or VALUES const.
  • ClickStack alerts, reverse private endpoints, and PgStateProperty (stopped, added in OpenAPI drift: 36 gaps between live spec and library #298) have no CLI validation surface.
  • No VALUES consts on the hundreds of enums without a CLI surface.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions