Skip to content

fix(admin): drop standalone max_budget_usd contract - #259

Merged
moonming merged 5 commits into
mainfrom
fix/issue-255-budget-contract
May 12, 2026
Merged

fix(admin): drop standalone max_budget_usd contract#259
moonming merged 5 commits into
mainfrom
fix/issue-255-budget-contract

Conversation

@moonming

@moonming moonming commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • reject max_budget_usd on standalone admin ApiKey writes and stop exposing it from the standalone admin read contract
  • keep the internal ApiKey shape intact so managed/control-plane budget paths are not coupled to this standalone admin surface cleanup
  • update the standalone admin OpenAPI, docs, and e2e coverage to pin the CP-owned budget boundary

Summary by CodeRabbit

  • Bug Fixes

    • Standalone deployments now reject per-key USD budget fields; requests including that field return 400.
    • Admin API responses no longer expose the removed budget field and return API key data in a public-facing representation.
  • Documentation

    • Clarified that per-key budget enforcement is control-plane (managed) only and removed standalone budget claims from the feature matrix.
  • Tests

    • E2E and unit tests updated to assert the removed field is rejected and absent from the OpenAPI/schema.

Review Change Stack

Copilot AI review requested due to automatic review settings May 12, 2026 04:16
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 31f538c0-151c-44b7-a882-a778f10d8f6a

📥 Commits

Reviewing files that changed from the base of the PR and between 3a63e31 and bd39374.

📒 Files selected for processing (1)
  • tests/e2e/src/cases/apikey-budget-e2e.test.ts

📝 Walkthrough

Walkthrough

The PR removes max_budget_usd from the standalone admin API and data model, updates OpenAPI and docs to state per-key budgets are control-plane managed, and changes handlers/tests so standalone rejects max_budget_usd writes with 400 responses.

Changes

Standalone API budget enforcement model

Layer / File(s) Summary
Architecture and API contract documentation
README.md, docs/api-admin.md
README specifies enforcement via managed CP /dp/budget_check (5s LRU) and that standalone lacks local budget authoring; admin docs remove max_budget_usd from examples and state it is not part of the gateway ApiKey schema.
Core data model and schema validation
crates/aisix-core/src/models/apikey.rs, crates/aisix-core/src/models/schema.rs, crates/aisix-core/src/models/mod.rs
ApiKey struct no longer includes max_budget_usd. JSON Schema disallows unknown max_budget_usd (with additionalProperties: false) and tests assert unknown-field rejection. Module docs updated to refer to per-key rate-limiting.
Admin handlers: public DTOs and request validation
crates/aisix-admin/src/apikeys_handlers.rs
Introduces StandaloneApiKeyBody, PublicApiKey, PublicApiKeyEntry; handlers return public entries and decode_apikey uses deny-unknown-fields deserialization, re-serializes for schema validation, and returns clearer BadRequest messages.
Admin API validation and handler tests
crates/aisix-admin/src/lib.rs
Tests now deserialize list responses before assertions; new tests assert POSTs containing max_budget_usd are rejected with 400 and that generated OpenAPI ApiKey schema excludes the field.
OpenAPI specification
crates/aisix-admin/src/openapi.rs
components.schemas.ApiKey removes max_budget_usd and references RateLimit for rate_limit.
E2E test coverage
tests/e2e/src/cases/apikey-budget-e2e.test.ts
E2E tests updated to assert standalone admin POSTs with max_budget_usd return HTTP 400 and an explanatory error mentioning max_budget_usd and control-plane management.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR removes max_budget_usd from the standalone admin API surface (write + read), while preserving the internal aisix_core::ApiKey shape so managed/control-plane budget plumbing remains decoupled from the standalone contract.

Changes:

  • Reject max_budget_usd in standalone admin ApiKey POST/PUT, and stop returning it from list/get/rotate responses via new PublicApiKey{,Entry} response shapes.
  • Update standalone admin OpenAPI schema to use PublicApiKey{,Entry} (excluding max_budget_usd) and add regression tests to pin this.
  • Update docs/README and adjust e2e coverage to reflect that budget policy is control-plane owned.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/e2e/src/cases/apikey-budget-e2e.test.ts Updates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.md Clarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.md Removes max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rs Switches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rs Adds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rs Implements PublicApiKey{,Entry} response types and rejects max_budget_usd in payload decoding.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 40 to 48
let caught: unknown;
try {
await admin.createApiKey({
key_hash: createHash("sha256").update("sk-neg-budget").digest("hex"),
key_hash: KEY_HASH,
allowed_models: ["*"],
max_budget_usd: -1,
max_budget_usd: 500.0,
});
} catch (e) {
caught = e;
Copilot AI review requested due to automatic review settings May 12, 2026 06:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

crates/aisix-core/src/models/apikey.rs:36

  • This PR’s description says the internal ApiKey shape should remain intact to avoid coupling managed/control-plane budget paths to the standalone admin cleanup, but max_budget_usd has been removed from the core ApiKey struct. Besides diverging from the stated intent, removing the field (while keeping #[serde(deny_unknown_fields)]) makes deserialization reject any stored ApiKey objects that still contain max_budget_usd. If managed mode or existing etcd data can still include this field, reintroduce it in the internal model (and hide it via standalone request/response types) or otherwise ensure backward-compatible parsing/migration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApiKey {
    /// SHA-256 hex of the plaintext bearer. Secondary-indexed for
    /// O(1) auth — the proxy hashes incoming bearers before lookup.
    pub key_hash: String,

    /// Whitelisted Model identifiers. cp-api stores them as model
    /// UUIDs; self-hosted dev fixtures may still use names — the DP
    /// does string equality and doesn't care which. An **empty
    /// array** denies every model (spec §3 authz rule).
    pub allowed_models: Vec<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rate_limit: Option<RateLimit>,

    /// etcd-key uuid; filled by the loader, never in the JSON payload.
    #[serde(skip)]
    pub(crate) runtime_id: String,
}

Comment thread crates/aisix-admin/src/lib.rs Outdated
.as_str()
.unwrap()
.contains("schema validation"));
.contains("unknown field"));
},
"rate_limit": { "$ref": "#/$defs/rate_limit" },
"max_budget_usd": { "type": "number", "minimum": 0 }
"rate_limit": { "$ref": "#/$defs/rate_limit" }
Copilot AI review requested due to automatic review settings May 12, 2026 08:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

@@ -30,12 +30,6 @@ pub struct ApiKey {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimit>,

},
"rate_limit": { "$ref": "#/$defs/rate_limit" },
"max_budget_usd": { "type": "number", "minimum": 0 }
"rate_limit": { "$ref": "#/$defs/rate_limit" }
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.

2 participants