Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions actions/setup/js/ai_credits_context.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ function parseMaxAICreditsExceededFromAuditLog(auditJsonlPathOverride) {
);
}

/**
* @param {unknown} entry
* @returns {boolean}
*/
function parseUnknownModelAICreditsFromAuditEntry(entry) {
if (!entry || typeof entry !== "object") return false;
const stack = [entry];
while (stack.length > 0) {
const node = stack.pop();
if (!node || typeof node !== "object") continue;
for (const [, value] of Object.entries(node)) {
if (value === UNKNOWN_MODEL_AI_CREDITS_TYPE) return true;
if (value && typeof value === "object") stack.push(value);
}
}
return false;
}

/**
* Detects an `unknown_model_ai_credits` error from the firewall audit log.
* This HTTP 400 error is emitted by the AWF API proxy when `maxAiCredits` is active and
Expand All @@ -268,20 +286,7 @@ function parseUnknownModelAICreditsFromAuditLog(auditJsonlPathOverride) {
auditJsonlPathOverride,
false,
content => content.includes(UNKNOWN_MODEL_AI_CREDITS_TYPE),
(acc, entry) => {
if (acc) return true;
if (!entry || typeof entry !== "object") return false;
const stack = [entry];
while (stack.length > 0) {
const node = stack.pop();
if (!node || typeof node !== "object") continue;
for (const [, value] of Object.entries(node)) {
if (value === UNKNOWN_MODEL_AI_CREDITS_TYPE) return true;
if (value && typeof value === "object") stack.push(value);
}
}
return false;
}
(acc, entry) => acc || parseUnknownModelAICreditsFromAuditEntry(entry)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[/zoom-out] The acc || parseUnknownModelAICreditsFromAuditEntry(entry) form matches the pattern at line 253 (parseMaxAICreditsExceededFromAuditLog's reducer) and is the right call: it short-circuits the entry-parse when acc is already true. By contrast, line 189 (parseMaxAICreditsFromAuditLog) still uses the entry-first form fn(entry) || acc, which evaluates fn even after the accumulator has been set. This PR inadvertently exposes that inconsistency — a follow-up to align line 189 would complete the pattern unification.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No change here. I kept the ai_credits_context.cjs extraction as-is and left the line 189 reducer ordering for a separate follow-up so this PR stays scoped to the review feedback on allowed_issue_fields.cjs.

);
}

Expand Down
2 changes: 1 addition & 1 deletion actions/setup/js/allowed_issue_fields.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function validateAllowedIssueFields(issueFields, allowedFields) {
if (!Array.isArray(allowedFields) || allowedFields.length === 0) {
return;
}
const allowedFieldSet = new Set(allowedFields.map(f => f.toLowerCase()));
const allowedFieldSet = new Set(allowedFields.map(field => field.toLowerCase()));
if (allowedFieldSet.has("*")) {
return;
}
Expand Down
5 changes: 5 additions & 0 deletions actions/setup/js/allowed_issue_fields.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ describe("validateAllowedIssueFields", () => {
expect(() => validateAllowedIssueFields(fields, ["title", "body"])).toThrow("ERR_VALIDATION");
});

it("throws when a field name is an empty string", () => {
const fields = [{ name: "", value: "New Title" }];
expect(() => validateAllowedIssueFields(fields, ["title", "body"])).toThrow("ERR_VALIDATION");
});

it("does not throw when allowedFields is empty (no restriction)", () => {
const fields = [{ name: "milestone", value: "v1.0" }];
expect(() => validateAllowedIssueFields(fields, [])).not.toThrow();
Expand Down