Skip to content

[code-simplifier] Simplify AI credits audit log parsing: extract shared helper, eliminate double file read#37731

Merged
pelikhan merged 4 commits into
mainfrom
simplify/ai-credits-audit-log-parsing-88a1fa719fce87c4
Jun 8, 2026
Merged

[code-simplifier] Simplify AI credits audit log parsing: extract shared helper, eliminate double file read#37731
pelikhan merged 4 commits into
mainfrom
simplify/ai-credits-audit-log-parsing-88a1fa719fce87c4

Conversation

@github-actions

@github-actions github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

What changed

Two production files simplified after analysis from scope-filter and simplification-scout agents. All public-API signatures and runtime behavior are preserved.


actions/setup/js/ai_credits_context.cjs

A — Extract iterateAuditEntries() scaffolding helper
parseMaxAICreditsFromAuditLog and parseAICreditsErrorInfoFromAuditLog shared ~40 lines of near-identical JSONL boilerplate (path resolution, existsSync, readFileSync, line-split loop, JSON.parse with catch, outer try/catch). Extracted into a generic iterateAuditEntries(pathOverride, defaultValue, contentGuard, accumulate) helper. Each caller now supplies only its per-entry accumulation logic.

B — Eliminate double audit log read in resolveAICreditsFailureState
Previously called both parse functions independently (2× existsSync + 2× readFileSync + 2× full line iteration for the same file). Added parseAuditLogCombined() — a single-pass reader that accumulates { aiCredits, maxAICredits, rateLimitError } in one sweep. resolveAICreditsFailureState now calls this once.

C — Remove dead || fallback in resolveFirewallAuditLogPath
candidateBases[0] || "/tmp/gh-aw/sandbox/firewall/audit" was unreachable: two unconditional pushes always populate the array. Removed the dead fallback arm.

D — Inner loop for filename probing
Replaced two copy-pasted log.jsonl / audit.jsonl probe blocks with for (const filename of ["log.jsonl", "audit.jsonl"]), making the try-each-filename intent explicit.

E — Follow-up robustness/typecheck fixes

  • Seed accumulator defaults are explicitly typed as boolean-compatible objects to satisfy tsc --noEmit.
  • iterateAuditEntries() now defensively ignores undefined accumulator returns, preserving prior accumulated state.
  • Added JSDoc rationale for why parseAuditLogCombined() intentionally does not use a contentGuard (single full pass is still cheaper because rate-limit signal detection must scan all entries).

actions/setup/js/check_daily_aic_workflow_guardrail.cjs

  • Collapse shouldSkipDailyAICGuardrail into a single boolean outcome with clearer named booleans
  • Remove dead || 0 in formatInteger, while keeping explicit numeric narrowing for type safety
  • Move let runs = [] inside loop body as const runs — was declared before the while-loop but unconditionally reassigned on every iteration and never read after the loop
  • Preserve existing guardrail log shape by explicitly logging candidateRunsCount and inspectedRunsCount (without broad spreading of summaryMeta)

Validation

  • Behavior is preserved: all public exports, return types, and control flow are identical
  • npm run lint:cjs passes
  • npm run typecheck passes
  • npm run test:js passes
  • Targeted check_daily_aic_workflow_guardrail.test.cjs passes
  • Final automated review/security validation passes

Source references

References: §27116645729

> Generated by 🔧 Code Simplifier · 1.5K AIC · ⌖ 27.5 AIC · ⊞ 20.9K ·
> - [x] expires on Jun 8, 2026, 9:05 PM UTC-08:00

…it log read

ai_credits_context.cjs:
- Extract iterateAuditEntries() generic JSONL scaffolding helper (path
  resolution, existsSync, readFileSync, line-split loop, JSON.parse with
  catch, outer try/catch) — eliminates ~40 lines of near-identical boilerplate
  duplicated across parseMaxAICreditsFromAuditLog and
  parseAICreditsErrorInfoFromAuditLog
- Add parseAuditLogCombined() single-pass helper that accumulates all three
  fields (aiCredits, maxAICredits, rateLimitError) in one file read
- resolveAICreditsFailureState now calls parseAuditLogCombined() once instead
  of calling two separate log-parse functions (was: 2x existsSync + 2x
  readFileSync + 2x full line iteration on every failure-state resolution)
- Use inner loop over ['log.jsonl', 'audit.jsonl'] in resolveFirewallAuditLogPath
  to replace two copy-pasted probe blocks (simplification D)
- Remove unreachable candidateBases[0] || fallback in resolveFirewallAuditLogPath —
  two unconditional pushes guarantee the array is never empty (simplification C)
- Document intentional asymmetry: parseAICreditsErrorInfoFromAuditLog has no
  content-guard fast-path because AI_CREDITS_RATE_LIMIT_TEXT_FIELDS contains
  ubiquitous field names; a pre-scan would almost never skip iteration

check_daily_aic_workflow_guardrail.cjs:
- Collapse shouldSkipDailyAICGuardrail into a single boolean expression
- Remove unreachable Math.round(value || 0) dead code in formatInteger —
  Number.isFinite already excludes non-finite values; 0/-0 are unaffected
- Move let runs = [] inside the while-loop body as const runs — was declared
  before the loop but unconditionally reassigned on every iteration
- Use ...summaryMeta spread in logDailyGuardrail call to avoid re-listing
  the same two property names already in the summaryMeta object

All public-API signatures and behavior preserved. No logic changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review June 8, 2026 05:10
Copilot AI review requested due to automatic review settings June 8, 2026 05:10

Copilot AI left a comment

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.

Pull request overview

Refactors the AI Credits audit-log parsing and daily AIC guardrail logic to reduce duplication and avoid redundant file reads, while preserving the existing public API surface and behavior.

Changes:

  • Extracts a shared JSONL iteration helper in ai_credits_context.cjs and introduces a single-pass combined parser to avoid double-reading the firewall audit log.
  • Simplifies several small logic patterns in the daily AIC workflow guardrail (boolean return, dead fallback removal, tighter scoping, and object spread).
Show a summary per file
File Description
actions/setup/js/ai_credits_context.cjs Extracts shared JSONL parsing helper and consolidates audit-log parsing into a single pass for failure-state resolution.
actions/setup/js/check_daily_aic_workflow_guardrail.cjs Simplifies guardrail conditionals and minor dead-code patterns; improves local variable scoping and logging call shape.

Copilot's findings

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #37731 does not have the 'implementation' label (has_implementation_label=false) and has 0 new lines of code in business logic directories (well under the 100-line threshold). No custom config present.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

🧪 Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. PR #37731 only changed actions/setup/js/ai_credits_context.cjs (+81/-42) and actions/setup/js/check_daily_aic_workflow_guardrail.cjs (+8/-10) — neither is a test file. Test Quality Sentinel skipped.

@github-actions github-actions Bot mentioned this pull request Jun 8, 2026

@github-actions github-actions Bot left a comment

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.

Skills-Based Review 🧠

Applied /tdd, /zoom-out, and /improve-codebase-architecture — approving with minor observations.

📋 Key Themes & Highlights

Key Themes

  • Test coverage gap: iterateAuditEntries is the new core abstraction but ai_credits_context.cjs has no test file (see inline comment).
  • Undocumented logging change: ...summaryMeta spread adds truncatedByRateLimit to the logDailyGuardrail call; this is correct context but is a behavioural diff from the old explicit listing.
  • Missing rationale for null contentGuard in parseAuditLogCombined: the design choice is sound (one full pass < two guarded passes) but the JSDoc does not explain it.

Positive Highlights

  • ✅ Dead-code removals are all correct: the || "/tmp/gh-aw/sandbox/firewall/audit" fallback is provably unreachable, and || 0 inside a Number.isFinite guard is provably a no-op.
  • const runs scoping fix is textbook; the let declaration before the loop was a false signal of shared state.
  • iterateAuditEntries accumulator callbacks faithfully preserve last-wins semantics for aiCredits/maxAICredits and monotonic-true for rateLimitError.
  • parseAuditLogCombined correctly eliminates the 2× file read in resolveAICreditsFailureState with no behavioural change to the returned values.
  • ✅ The contentGuard asymmetry between parseMaxAICreditsFromAuditLog and parseAICreditsErrorInfoFromAuditLog is well-documented and intentional.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 236.6 AIC · ⌖ 25.6 AIC

* @returns {T}
*/
function parseMaxAICreditsFromAuditLog(auditJsonlPathOverride) {
function iterateAuditEntries(auditJsonlPathOverride, defaultValue, contentGuard, accumulate) {

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.

[/tdd] iterateAuditEntries is a non-trivial pure function with several independently-testable edge cases — but no test file exists for this module (unlike peer modules such as parse_mcp_gateway_log.test.cjs, error_codes.test.cjs, etc.).

💡 Suggested edge cases to cover
// 1. Missing file → returns defaultValue
// 2. Empty file → returns defaultValue
// 3. contentGuard returns false → returns defaultValue without parsing
// 4. All lines are malformed JSON → returns defaultValue unchanged
// 5. Accumulate is called per-entry in order
// 6. Combined: only error-info entries do not bleed into maxAICredits field

The parseAuditLogCombined single-pass semantics (both parsers run on every line) are particularly worth a test to guard against future regressions when either entry-parser changes.

logDailyGuardrail("Completed AIC inspection window", {
candidateRunsCount: summaryMeta.candidateRunsCount,
inspectedRunsCount: summaryMeta.inspectedRunsCount,
...summaryMeta,

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 spread silently adds a third field to the log that was not there before: truncatedByRateLimit is now emitted alongside candidateRunsCount and inspectedRunsCount. This is likely desirable (it is relevant context), but it is a behavioural change — log consumers or audit tooling may not expect it.

💡 Options

If the intent is truly to spread everything, add a short comment:

// Spreads candidateRunsCount, inspectedRunsCount, truncatedByRateLimit
...summaryMeta,

If only the two original fields were intended, keep them explicit:

candidateRunsCount: summaryMeta.candidateRunsCount,
inspectedRunsCount: summaryMeta.inspectedRunsCount,

The JSDoc @type above summaryMeta pins its shape, so future field additions would still require a deliberate edit — the spread is safe today, just undocumented as a change.

Comment thread actions/setup/js/ai_credits_context.cjs Outdated
* @returns {{ aiCredits: string, maxAICredits: string, rateLimitError: boolean }}
*/
function parseAuditLogCombined(auditJsonlPathOverride) {
return iterateAuditEntries(

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.

[/improve-codebase-architecture] parseAuditLogCombined passes null for contentGuard, which drops the fast-path that parseMaxAICreditsFromAuditLog uses (the /(?:max_ai_credits|maxAiCredits)/ content scan). The trade-off is correct — you must iterate for the rate-limit signal anyway, so one full pass beats two guarded passes — but the JSDoc does not explain this reasoning.

💡 Suggested JSDoc addition
/**
 * Single-pass combined read of the audit log...
 *
 * No contentGuard is applied: because the rate-limit signal must be scanned
 * regardless, a full single pass is cheaper than two guarded passes even when
 * max_ai_credits entries are absent.
 */

A future maintainer seeing null for contentGuard alongside an accumulator that also handles maxAICredits might re-add the guard unnecessarily without this context.

@github-actions github-actions Bot left a comment

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.

Non-blocking observations on iterateAuditEntries

The core refactor is correct — single-pass combined read eliminates the double file I/O, accumulate semantics match the original last-value-wins behavior for all three fields, and the check_daily_aic_workflow_guardrail.cjs cleanups are safe. Two advisory concerns about the new generic abstraction are noted inline.

🔎 Code quality review by PR Code Quality Reviewer · 20.7 AIC · ⌖ 13.4 AIC

let parsedMaxAICredits = "";
if (!content.trim()) return defaultValue;
if (contentGuard && !contentGuard(content)) return defaultValue;
let result = defaultValue;

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.

defaultValue aliased into result — mutation-safe only by convention, not contract. Any accumulate callback that mutates acc in-place instead of returning a fresh object will silently corrupt the defaultValue reference passed by the caller.

💡 Details and suggested fix

Currently all callers pass inline object literals ({ aiCredits: "", ... }), so each call site creates a fresh object and corruption is limited to a single invocation. However the function is a new generic abstraction that future contributors will re-use. Nothing in the signature or JSDoc prevents passing a module-level constant as defaultValue, which would be permanently corrupted on the first mutating accumulate.

Simplest fix: note the invariant in the JSDoc and optionally shallow-clone the seed:

// Option A: document the contract
/**
 * `@param` {(acc: T, entry: unknown) => T} accumulate - MUST return a new value/object; must not mutate acc.
 */

// Option B: defensive clone (only needed if T is an object)
let result = (defaultValue !== null && typeof defaultValue === "object")
  ? { ...defaultValue }
  : defaultValue;

Without this, the first caller that accidentally writes acc.field = value; return acc; will introduce a silent state-pollution bug that is very hard to diagnose.

Comment thread actions/setup/js/ai_credits_context.cjs Outdated
const entry = JSON.parse(trimmed);
const value = parseMaxAICreditsFromAuditEntry(entry);
if (value) parsedMaxAICredits = value;
result = accumulate(result, JSON.parse(trimmed));

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.

accumulate returning undefined silently breaks the defaultValue fallback contract. If any accumulate callback forgets a return statement, result becomes undefined and iterateAuditEntries returns undefined — not defaultValue — because returning undefined does not throw and the outer catch never fires.

💡 Why this matters and how to fix it

The outer try/catch at line 165 only rescues thrown exceptions; a return undefined from accumulate silently escapes it. The propagation chain:

  1. accumulate(result, entry) returns undefinedresult = undefined
  2. Next iteration: accumulate(undefined, entry) tries to read acc.aiCredits → throws TypeError
  3. Inner catch ignores it; result stays undefined
  4. iterateAuditEntries returns undefined instead of T
  5. Caller destructures: const { aiCredits } = parseAuditLogCombined()TypeError: Cannot destructure property ... of undefined

This crashes resolveAICreditsFailureState with an unhandled exception — not a silent default.

Fix options:

// Option A: guard the return value
result = accumulate(result, JSON.parse(trimmed)) ?? result;

// Option B: add an assertion comment in JSDoc
// `@param` {(acc: T, entry: unknown) => T} accumulate - must always return a defined value; returning undefined is a bug.

Option A is more defensive and maintains the invariant automatically.

@pelikhan

pelikhan commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

@copilot review all comments and the reviews, create a commit, then apply the copilot review skill.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

CI is failing on js-typecheck and lint-js; please address those failures and push an update.

Generated by 👨‍🍳 PR Sous Chef · 35.1 AIC · ⌖ 1 AIC · ⊞ 17K ·

Copilot AI and others added 3 commits June 8, 2026 06:00
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

@copilot review all comments and the reviews, create a commit, then apply the copilot review skill.

Done. I reviewed all in-scope comments/reviews, addressed the CI failures (js-typecheck and lint-js), and pushed follow-up commits. Latest commit: 6249726.

Copilot AI requested a review from pelikhan June 8, 2026 06:03
@pelikhan pelikhan merged commit 74391da into main Jun 8, 2026
18 of 19 checks passed
@pelikhan pelikhan deleted the simplify/ai-credits-audit-log-parsing-88a1fa719fce87c4 branch June 8, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants