{"record":{"id":"ef947fa4728867f1","repo":"ruvnet/ruflo","slug":"actualusd-must-be-a-non-negative-finite-number","errorCode":null,"errorMessage":"actualUsd must be a non-negative finite number","messagePattern":"actualUsd must be a non-negative finite number","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/business-pods/bbs-budget-tracker.ts","lineNumber":299,"sourceCode":"      return { ok: true, reservationId, remainingAfterReserve: remaining };\n    } catch (err) {\n      if (transactionOpen) {\n        try { rollbackStmt.run(); } catch { /* already-rolled */ }\n      }\n      throw err;\n    }\n  }\n\n  /**\n   * Commit the reservation with the actual cost. Late commits (expired\n   * before commit landed) ARE accepted, transitioned to\n   * 'committed_post_expiry', charged to the budget, and surfaced via\n   * `warned: 'COMMIT_AFTER_EXPIRY'` plus a `reservation.committed_post_expiry`\n   * audit emit. See ADR-164.1 §5.3 + §8.1.\n   */\n  commit(reservationId: string, actualUsd: number): CommitResult {\n    if (!Number.isFinite(actualUsd) || actualUsd < 0) {\n      throw new Error('actualUsd must be a non-negative finite number');\n    }\n    const nowMs = this.clock();\n\n    const beginStmt = this.db.prepare('BEGIN IMMEDIATE');\n    const commitStmt = this.db.prepare('COMMIT');\n    const rollbackStmt = this.db.prepare('ROLLBACK');\n\n    beginStmt.run();\n    let transactionOpen = true;\n    try {\n      const row = this.db\n        .prepare(\n          `SELECT state, room_id, estimated_usd, reserved_at, expires_at\n             FROM bbs_budget_reservations\n             WHERE reservation_id = ?`,\n        )\n        .get(reservationId) as\n        | { state: string; room_id: string; estimated_usd: number; reserved_at: number; expires_at: number }","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/business-pods/bbs-budget-tracker.ts#L281-L317","documentation":"BBS budget tracker commit() rejects its actualUsd with the same numeric contract as reserve(): finite and non-negative, validated before the transaction opens. The guard protects the commit path (ADR-164.1 §5.3) which charges the actual cost against the monthly budget and may transition an expired reservation to 'committed_post_expiry'.","triggerScenarios":"Calling tracker.commit(reservationId, actualUsd) with NaN/±Infinity/negative actualUsd — e.g. usage math that produced NaN, an API returning a negative refund/adjustment that was passed through, or a string amount from a metering event.","commonSituations":"Metering pipelines forwarding raw provider numbers without sanitization; negative adjustment/refund events that should go through a different path, not commit(); JSON deserialization yielding strings for numeric fields.","solutions":["Sanitize the metered amount before committing: const actual = Number.isFinite(x) && x >= 0 ? x : 0 (or drop the event and alert)","Handle refunds/credits through a dedicated accounting path instead of negative commit() values","Trace NaN sources in usage aggregation (undefined * number, parseFloat of malformed strings)","Add a unit test asserting commit rejects NaN, Infinity, and -1 so regressions surface early"],"exampleFix":"// before\ntracker.commit(reservationId, usage.actualUsd /* NaN when usage row missing */);\n// after\nconst actual = Number(usage.actualUsd);\nif (!Number.isFinite(actual) || actual < 0) throw new TypeError(`bad metered cost: ${usage.actualUsd}`);\ntracker.commit(reservationId, actual);","handlingStrategy":"validation","validationCode":"const actual = Number(meteredEvent.actualUsd);\nif (!Number.isFinite(actual) || actual < 0) {\n  deadLetter(meteredEvent); // never feed bad metering into commit()\n} else {\n  tracker.commit(reservationId, actual);\n}","typeGuard":"function isCommitUsd(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0;\n}","tryCatchPattern":null,"preventionTips":["Sanitize metered values at ingestion; dead-letter malformed events instead of passing them on","Route refunds/credits through a dedicated path — commit() is not a negative-amount API","Assert Number.isFinite on all division results in usage aggregation"],"tags":["budget","validation","numeric","bbs","pod"],"backgroundTag":"invalid-numeric-input","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}