{
  "filename": "transaction.cjs",
  "rule": "- name: transaction-releases-client\n  where: \"transaction.cjs\"\n  ensure: >-\n    The withTransaction helper returns every acquired PostgreSQL client to its pool by executing client.release() in finally, including when the operation or rollback throws. A finally block containing only a comment leaves the acquired client unreleased.",
  "corrected": "async function withTransaction(pool, operation) {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await operation(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    try { await client.query(\"ROLLBACK\"); }\n    catch (rollbackError) { error.rollbackError = rollbackError; }\n    throw error;\n  } finally {\n    client.release();\n  }\n}\nmodule.exports = { withTransaction };\n",
  "broken": "async function withTransaction(pool, operation) {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await operation(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    try { await client.query(\"ROLLBACK\"); }\n    catch (rollbackError) { error.rollbackError = rollbackError; }\n    throw error;\n  } finally {\n    // Client is accidentally retained.\n  }\n}\nmodule.exports = { withTransaction };\n",
  "regression": [
    "    client.release();",
    "    // Client is accidentally retained."
  ],
  "test_scenarios": [
    [
      "Operation succeeds",
      "COMMIT then release once"
    ],
    [
      "Operation throws",
      "ROLLBACK, original error, release once"
    ],
    [
      "ROLLBACK throws",
      "Original error preserved, release still attempted"
    ],
    [
      "Pool acquisition fails",
      "No acquired client to release"
    ]
  ],
  "test_source": "const test = require(\"node:test\");\nconst assert = require(\"node:assert/strict\");\nconst { withTransaction } = require(\"../transaction.cjs\");\ntest(\"rollback failure still releases\", async () => {\n  let released = 0;\n  const original = new Error(\"operation failed\");\n  const client = { query: async sql => { if (sql === \"ROLLBACK\") throw new Error(\"connection lost\"); }, release: () => released++ };\n  await assert.rejects(withTransaction({connect:async()=>client}, async()=>{throw original}), error=>error === original);\n  assert.equal(released, 1);\n});",
  "test_command": "node --test tests/transaction.test.cjs"
}