Report quota resource categories in CLI errors
This commit is contained in:
parent
eb8dc3bf12
commit
1c4b045a6a
4 changed files with 87 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"kind": "disasmer-filtered-public-tree",
|
"kind": "disasmer-filtered-public-tree",
|
||||||
"source_commit": "f4707862d419b08f13d072a31f8f5e3cb9ddda66",
|
"source_commit": "28a8b4578c0b3fd289b938c1e6f07cc15e6848b2",
|
||||||
"release_name": "dryrun-f4707862d419",
|
"release_name": "dryrun-28a8b4578c0b",
|
||||||
"filtered_out": [
|
"filtered_out": [
|
||||||
"private/**",
|
"private/**",
|
||||||
"experiments/**",
|
"experiments/**",
|
||||||
|
|
|
||||||
|
|
@ -2759,7 +2759,7 @@ fn cli_error_summary_with_default(message: &str, default_category: &'static str)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cli_error_summary_for_category(category: &'static str, message: &str) -> Value {
|
fn cli_error_summary_for_category(category: &'static str, message: &str) -> Value {
|
||||||
json!({
|
let mut summary = json!({
|
||||||
"category": category,
|
"category": category,
|
||||||
"stable_exit_code": cli_error_exit_code(category),
|
"stable_exit_code": cli_error_exit_code(category),
|
||||||
"process_exit_code_applied": false,
|
"process_exit_code_applied": false,
|
||||||
|
|
@ -2767,7 +2767,56 @@ fn cli_error_summary_for_category(category: &'static str, message: &str) -> Valu
|
||||||
"message": message,
|
"message": message,
|
||||||
"safe_failure": true,
|
"safe_failure": true,
|
||||||
"next_actions": cli_error_next_actions(category),
|
"next_actions": cli_error_next_actions(category),
|
||||||
})
|
});
|
||||||
|
if category == "quota" {
|
||||||
|
if let Some(object) = summary.as_object_mut() {
|
||||||
|
object.insert(
|
||||||
|
"resource_category".to_owned(),
|
||||||
|
json!(
|
||||||
|
quota_error_resource_category(message).unwrap_or_else(|| "unknown".to_owned())
|
||||||
|
),
|
||||||
|
);
|
||||||
|
object.insert("community_tier_language".to_owned(), json!(true));
|
||||||
|
object.insert("private_abuse_heuristics_exposed".to_owned(), json!(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quota_error_resource_category(message: &str) -> Option<String> {
|
||||||
|
let lower = message.to_ascii_lowercase();
|
||||||
|
for marker in [
|
||||||
|
"resource limit exceeded for ",
|
||||||
|
"quota unavailable for ",
|
||||||
|
"quota exceeded for ",
|
||||||
|
"limit exceeded for ",
|
||||||
|
"metering limit exceeded for ",
|
||||||
|
"quota limit for ",
|
||||||
|
"resource category ",
|
||||||
|
"resource=",
|
||||||
|
"resource:",
|
||||||
|
] {
|
||||||
|
if let Some(index) = lower.find(marker) {
|
||||||
|
let start = index + marker.len();
|
||||||
|
let rest = &message[start..];
|
||||||
|
let value: String = rest
|
||||||
|
.chars()
|
||||||
|
.skip_while(|ch| ch.is_ascii_whitespace())
|
||||||
|
.take_while(|ch| {
|
||||||
|
ch.is_ascii_alphanumeric()
|
||||||
|
|| *ch == '_'
|
||||||
|
|| *ch == '-'
|
||||||
|
|| *ch == '.'
|
||||||
|
|| *ch == ':'
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let value = value.trim_matches(|ch: char| ch == '.' || ch == ':' || ch == ',');
|
||||||
|
if !value.is_empty() {
|
||||||
|
return Some(value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn classify_cli_error_message(message: &str) -> &'static str {
|
fn classify_cli_error_message(message: &str) -> &'static str {
|
||||||
|
|
@ -5679,7 +5728,11 @@ mod tests {
|
||||||
20,
|
20,
|
||||||
),
|
),
|
||||||
("unauthorized tenant action", "authorization", 21),
|
("unauthorized tenant action", "authorization", 21),
|
||||||
("quota unavailable: resource limit exceeded", "quota", 22),
|
(
|
||||||
|
"quota unavailable: resource limit exceeded for api_calls",
|
||||||
|
"quota",
|
||||||
|
22,
|
||||||
|
),
|
||||||
("policy denied native command execution", "policy", 23),
|
("policy denied native command execution", "policy", 23),
|
||||||
(
|
(
|
||||||
"scheduler placement failed: no capable node for placement: project mismatch",
|
"scheduler placement failed: no capable node for placement: project mismatch",
|
||||||
|
|
@ -5714,6 +5767,11 @@ mod tests {
|
||||||
assert_eq!(summary["safe_failure"], true);
|
assert_eq!(summary["safe_failure"], true);
|
||||||
assert_eq!(summary["process_exit_code_applied"], false);
|
assert_eq!(summary["process_exit_code_applied"], false);
|
||||||
assert!(summary["next_actions"].as_array().unwrap().len() >= 2);
|
assert!(summary["next_actions"].as_array().unwrap().len() >= 2);
|
||||||
|
if category == "quota" {
|
||||||
|
assert_eq!(summary["resource_category"], "api_calls");
|
||||||
|
assert_eq!(summary["community_tier_language"], true);
|
||||||
|
assert_eq!(summary["private_abuse_heuristics_exposed"], false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6118,6 +6176,12 @@ mod tests {
|
||||||
assert_eq!(rejected["error_category"], "quota");
|
assert_eq!(rejected["error_category"], "quota");
|
||||||
assert_eq!(rejected["stable_exit_code"], 22);
|
assert_eq!(rejected["stable_exit_code"], 22);
|
||||||
assert_eq!(rejected["machine_error"]["category"], "quota");
|
assert_eq!(rejected["machine_error"]["category"], "quota");
|
||||||
|
assert_eq!(rejected["machine_error"]["resource_category"], "api_calls");
|
||||||
|
assert_eq!(rejected["machine_error"]["community_tier_language"], true);
|
||||||
|
assert_eq!(
|
||||||
|
rejected["machine_error"]["private_abuse_heuristics_exposed"],
|
||||||
|
false
|
||||||
|
);
|
||||||
assert!(rejected["machine_error"]["next_actions"]
|
assert!(rejected["machine_error"]["next_actions"]
|
||||||
.as_array()
|
.as_array()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,12 @@ async function main() {
|
||||||
const report = JSON.parse(result.stdout);
|
const report = JSON.parse(result.stdout);
|
||||||
assert.strictEqual(report.status, "coordinator_rejected");
|
assert.strictEqual(report.status, "coordinator_rejected");
|
||||||
assert.strictEqual(report.run_start.machine_error.category, "quota");
|
assert.strictEqual(report.run_start.machine_error.category, "quota");
|
||||||
|
assert.strictEqual(report.run_start.machine_error.resource_category, "api_calls");
|
||||||
|
assert.strictEqual(report.run_start.machine_error.community_tier_language, true);
|
||||||
|
assert.strictEqual(
|
||||||
|
report.run_start.machine_error.private_abuse_heuristics_exposed,
|
||||||
|
false
|
||||||
|
);
|
||||||
assert.strictEqual(report.run_start.machine_error.stable_exit_code, 22);
|
assert.strictEqual(report.run_start.machine_error.stable_exit_code, 22);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
report.run_start.machine_error.process_exit_code_applied,
|
report.run_start.machine_error.process_exit_code_applied,
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,11 @@ expect(
|
||||||
"log redaction criteria",
|
"log redaction criteria",
|
||||||
/Logs are capped, truncated honestly[\s\S]*preserve byte counts and truncation flags[\s\S]*Secret-like values are redacted[\s\S]*common token\/password\/bearer patterns/
|
/Logs are capped, truncated honestly[\s\S]*preserve byte counts and truncation flags[\s\S]*Secret-like values are redacted[\s\S]*common token\/password\/bearer patterns/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
criteria,
|
||||||
|
"quota resource-category criteria",
|
||||||
|
/Hitting a quota produces a clear error[\s\S]*resource category[\s\S]*private abuse heuristics[\s\S]*quota machine errors now extract the resource category/
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
cliFirstAcceptance,
|
cliFirstAcceptance,
|
||||||
"CLI-first acceptance report",
|
"CLI-first acceptance report",
|
||||||
|
|
@ -364,6 +369,11 @@ expect(
|
||||||
"CLI classifies machine-readable error categories",
|
"CLI classifies machine-readable error categories",
|
||||||
/fn classify_cli_error_message[\s\S]*"authentication"[\s\S]*"authorization"[\s\S]*"quota"[\s\S]*"policy"[\s\S]*"capability"[\s\S]*"connectivity"[\s\S]*"environment"[\s\S]*"program"/
|
/fn classify_cli_error_message[\s\S]*"authentication"[\s\S]*"authorization"[\s\S]*"quota"[\s\S]*"policy"[\s\S]*"capability"[\s\S]*"connectivity"[\s\S]*"environment"[\s\S]*"program"/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
cli,
|
||||||
|
"CLI exposes quota machine-error posture",
|
||||||
|
/fn cli_error_summary_for_category[\s\S]*resource_category[\s\S]*quota_error_resource_category[\s\S]*community_tier_language[\s\S]*private_abuse_heuristics_exposed[\s\S]*fn quota_error_resource_category[\s\S]*resource limit exceeded for /
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
cli,
|
cli,
|
||||||
"CLI reports stable error-code contract",
|
"CLI reports stable error-code contract",
|
||||||
|
|
@ -479,6 +489,8 @@ for (const [name, pattern] of [
|
||||||
["fake coordinator quota rejection", /quota unavailable: resource limit exceeded for api_calls/],
|
["fake coordinator quota rejection", /quota unavailable: resource limit exceeded for api_calls/],
|
||||||
["run uses JSON mode", /"run"[\s\S]*"--json"/],
|
["run uses JSON mode", /"run"[\s\S]*"--json"/],
|
||||||
["actual quota exit code", /assert\.strictEqual\(result\.code, 22/],
|
["actual quota exit code", /assert\.strictEqual\(result\.code, 22/],
|
||||||
|
["actual quota resource-category assertion", /machine_error\.resource_category, "api_calls"/],
|
||||||
|
["actual quota abuse-heuristics assertion", /machine_error\.private_abuse_heuristics_exposed,[\s\S]*false/],
|
||||||
["exit-code application in JSON", /process_exit_code_applied[\s\S]*true/],
|
["exit-code application in JSON", /process_exit_code_applied[\s\S]*true/],
|
||||||
]) {
|
]) {
|
||||||
expect(errorExitSmoke, name, pattern);
|
expect(errorExitSmoke, name, pattern);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue