{"record":{"id":"d775c8017854d96e","repo":"ruvnet/ruflo","slug":"duplicate-approval-id","errorCode":null,"errorMessage":"duplicate-approval-id","messagePattern":"duplicate-approval-id","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/engine.ts","lineNumber":112,"sourceCode":"  setBudget(limit: BudgetLimit): void {\n    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');\n    if (!Number.isFinite(limit.periodMs)\n      || [limit.maxCostUsd, limit.maxTokens].some((value) => (\n        value !== undefined && (!Number.isFinite(value) || value < 0)\n      ))) throw new Error('invalid-budget-limit');\n    const index = this.state.budgets.findIndex((item) => item.id === limit.id);\n    if (index >= 0) this.state.budgets[index] = structuredClone(limit);\n    else this.state.budgets.push(structuredClone(limit));\n  }\n\n  issueApproval(approval: Omit<PolicyApproval, 'uses' | 'issuedAt'> & { uses?: number; issuedAt?: number }): PolicyApproval {\n    if (approval.issuedBy === approval.principal) throw new Error('self-approval-forbidden');\n    if (this.approvalIssuerVerifier?.(approval.issuedBy) !== true) {\n      throw new Error('untrusted-approval-issuer');\n    }\n    const issuedAt = approval.issuedAt ?? this.now();\n    const record: PolicyApproval = { ...approval, issuedAt, uses: approval.uses ?? 0 };\n    if (this.state.approvals.some((item) => item.id === record.id)) throw new Error('duplicate-approval-id');\n    if (!record.id\n      || record.expiresAt <= issuedAt\n      || !Number.isInteger(record.maxUses)\n      || record.maxUses <= 0\n      || !Number.isInteger(record.uses)\n      || record.uses < 0\n      || record.uses > record.maxUses) throw new Error('invalid-approval');\n    this.state.approvals.push(record);\n    return structuredClone(record);\n  }\n\n  revokeApproval(id: string): boolean {\n    const approval = this.state.approvals.find((item) => item.id === id);\n    if (!approval || approval.revokedAt) return false;\n    approval.revokedAt = this.now();\n    return true;\n  }\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/engine.ts#L94-L130","documentation":"Approval ids must be unique across the engine's state: issueApproval() scans existing approvals and throws Error('duplicate-approval-id') when the incoming id already exists. Ids are not auto-generated or namespaced, so callers own uniqueness.","triggerScenarios":"A retry wrapper re-running the same issueApproval call with a deterministic id after a transient failure; rehydrating from persisted state and re-issuing approvals that were already loaded; two workflows deriving the same id from the same request hash.","commonSituations":"Idempotency-key reuse across runs; crash-recovery logic that replays issuance; deterministic ids for reproducible tests colliding with previously issued ones.","solutions":["Generate a fresh id per issuance (crypto.randomUUID()) unless you deliberately need deterministic ids.","For idempotent retries, check engine state for the id first and reuse the existing approval instead of re-issuing.","If a stale approval blocks a re-issue, revokeApproval(id) before issuing a replacement."],"exampleFix":"// before\nengine.issueApproval({ id: `appr-${requestId}`, ... }); // throws on retry\n\n// after\nconst id = `appr-${requestId}`;\ntry {\n  engine.issueApproval({ id, ... });\n} catch (err) {\n  if (!(err instanceof Error) || err.message !== 'duplicate-approval-id') throw err;\n  engine.revokeApproval(id);\n  engine.issueApproval({ id: `${id}-${Date.now()}`, ... });\n}","handlingStrategy":"try-catch","validationCode":"// deterministic-id retry path: probe state first (or track issued ids)\nif (issuedApprovalIds.has(id)) {\n  return existingApproval(id);\n}\nengine.issueApproval({ id, ...rest });\nissuedApprovalIds.add(id);","typeGuard":null,"tryCatchPattern":"try {\n  return engine.issueApproval(approval);\n} catch (err) {\n  if (err instanceof Error && err.message === 'duplicate-approval-id') {\n    // idempotent retry: fetch or revoke+reissue instead of failing\n    return lookupApproval(approval.id) ?? reissue(approval);\n  }\n  throw err;\n}","preventionTips":["Default to crypto.randomUUID() for approval ids unless determinism is required.","Wrap issuance retries in idempotency: remember issued ids, or check engine state before re-issuing.","Pair deterministic ids with a revoke-then-reissue flow for refresh scenarios."],"tags":["policy","approval","idempotency"],"backgroundTag":"duplicate-id","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}