Skip to content

Retire enrich_budget_not_found workaround in 0.3.0 (after server ≥0.1.25.6 rollout) #10

@amavashev

Description

@amavashev

Context

Shipped in v0.2.3 (PR #9, closes #8), the enrich_budget_not_found(err, unit) helper in `src/client.rs` rewrites `Error::Api.message` when the server returns a misleading `404 NOT_FOUND "Budget not found for provided scope: "` on wrong-unit reserve/decide/event calls. It was a workaround for the server's out-of-spec 404 on these paths.

The server-side root-cure is runcycles/cycles-server#79 (v0.1.25.6, branch `fix/reserve-unit-mismatch`). Once merged and deployed, wrong-unit requests return structured `400 UNIT_MISMATCH` with `details.{scope,requested_unit,expected_units}` directly from the server — no client-side enrichment needed.

Why this is a tracking issue, not an immediate fix

The workaround is forward-compatible with the server fix:

  • Wrong-unit case → server now returns `400 UNIT_MISMATCH` → the enrichment predicate (`status == 404 && code == NotFound && message.starts_with("Budget not found for provided scope")`) does not match → passes through unchanged. Users get the structured `details` from the server.
  • Truly-missing case → server still returns `404 NOT_FOUND` with the same marker → enrichment still fires and remains helpful (tells the user the unit they sent).

So there is no urgency. The workaround can stay indefinitely without harm. This issue is just a note to retire it cleanly in a future minor bump.

Scope of work (0.3.0)

When the fleet is known to be on `cycles-server` ≥ 0.1.25.6:

  • Delete the enrichment helper and its callers:
    • `src/client.rs:37` — remove `BUDGET_NOT_FOUND_MARKER` const.
    • `src/client.rs:42-73` — remove `enrich_budget_not_found` fn.
    • `src/client.rs:17` — drop the `use crate::models::enums::Unit;` import if it becomes unused.
    • `src/client.rs` — remove the four `.map_err(|e| enrich_budget_not_found(e, …))` chains on `create_reservation`, `create_reservation_with_metadata`, `decide`, `create_event`.
    • `src/client.rs::tests` — remove the 5 unit tests: `enrich_budget_not_found_adds_unit_hint`, `_uses_wire_format_for_unit`, `_ignores_non_matching_messages`, `_ignores_non_404_errors`, `_passes_through_other_error_kinds`.
    • `tests/client_test.rs` — remove the 4 wiremock tests: `create_reservation_404_budget_not_found_is_enriched_with_unit`, `decide_404_budget_not_found_is_enriched_with_unit`, `create_event_404_budget_not_found_is_enriched_with_unit`, `create_reservation_404_other_not_found_is_not_enriched`.
  • Add typed surface for the new `400 UNIT_MISMATCH` details payload:
    • Extend `Error::Api.details` usage OR add a dedicated `Error::UnitMismatch { scope, requested_unit, expected_units, request_id }` variant. Prefer the dedicated variant — it parallels `Error::BudgetExceeded` and lets users pattern-match without parsing `serde_json::Value`.
    • Parse `details.scope`, `details.requested_unit`, `details.expected_units` from the server's `ErrorResponse` in `parse_error_response` when `code == Some(UnitMismatch) && status == 400`. Spec says the details payload is `SHOULD`, so make all three fields tolerant (`Option<…>`).
    • Add `Error::expected_units()` accessor and update `Error::is_budget_exceeded()`-style helpers if needed.
  • Tests:
    • New wiremock integration test: reserve → `400 UNIT_MISMATCH` with full `details` payload → assert the new error variant is populated with `scope`, `requested_unit`, `expected_units`.
    • Regression test: a `400 UNIT_MISMATCH` without `details` (legacy commit path per spec line 56's SHOULD wording) still deserializes cleanly with `None` fields.
    • Regression test: a `400 UNIT_MISMATCH` for a commit-vs-reservation mismatch continues to work (unchanged code path, but worth a fixture).
  • Docs:
    • Remove the `(scope, unit)` invariant callouts from `src/models/common.rs`, `src/lifecycle.rs`, `examples/with_cycles_usage.rs`, and `README.md` — OR rewrite them to describe the new typed error surface instead of the old 404 confusion.
    • `CHANGELOG.md` 0.3.0 entry: `Removed` the workaround (note it as a breaking change only if the `Error` variant changes — in which case bump to 0.3.0 minor as planned, not a patch).
    • `AUDIT.md` 0.3.0 section referencing this issue and cross-linking cycles-server#79.
  • Version: 0.2.x → 0.3.0 in `Cargo.toml` (breaking if `Error` gains a new non-exhaustive variant users were matching exhaustively; otherwise 0.2.4 is fine).

Pre-conditions for merging this

  1. cycles-server#79 merged and released as 0.1.25.6 (or later).
  2. All production deployments of `cycles-server` we know about are on ≥ 0.1.25.6. For self-hosted users, the 0.3.0 release notes should state the minimum server version.
  3. `cycles-protocol-v0.yaml` lines 53-64 (branch `fix/reserve-unit-mismatch`) merged to main — the `400 UNIT_MISMATCH` on reserve is then spec-normative.

Non-goals

  • Not a breaking change to the public `CyclesClient` API surface (methods, builders, config). Only `Error` variants may grow.
  • Not touching commit/release/extend/list/get — those were never affected by the workaround.
  • Not retiring the `(scope, unit)` documentation invariant — the invariant is still true (budgets are still indexed by `(scope, unit)`), only the diagnostic pathway changes.

Related


Low priority — the workaround is forward-compatible and causes no harm. Pick up when working on an unrelated 0.3.0 change, or if the `Error` enum is being restructured for another reason.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions