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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@moonmingmoonming commented May 12, 2026

Copy link
Copy Markdown
Member

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

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

coderabbitaiBot 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.

CopilotAI 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
FileDescription
tests/e2e/src/cases/apikey-budget-e2e.test.tsUpdates e2e coverage to expect standalone admin rejection of max_budget_usd.
README.mdClarifies that budgets are managed-mode/control-plane owned and not locally authored in standalone.
docs/api-admin.mdRemoves max_budget_usd from standalone admin examples and documents rejection behavior.
crates/aisix-admin/src/openapi.rsSwitches ApiKey request/response schema refs to PublicApiKey{,Entry} without max_budget_usd.
crates/aisix-admin/src/lib.rsAdds tests asserting max_budget_usd is excluded from responses and rejected on create; validates OpenAPI schema.
crates/aisix-admin/src/apikeys_handlers.rsImplements 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;
CopilotAI review requested due to automatic review settings May 12, 2026 06:55

CopilotAI 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 threadcrates/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" }
CopilotAI review requested due to automatic review settings May 12, 2026 08:23

CopilotAI 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 freeto 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

@moonming