{"record":{"id":"81a82a9f2c3d27d3","repo":"ruvnet/ruflo","slug":"amendment-already-resolved-amendment-status","errorCode":null,"errorMessage":"Amendment already resolved: ${amendment.status}","messagePattern":"Amendment already resolved: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/guidance/src/meta-governance.ts","lineNumber":409,"sourceCode":"      throw new Error(`Amendment not found: ${amendmentId}`);\n    }\n    if (amendment.status !== 'proposed') {\n      throw new Error(`Cannot vote on amendment with status: ${amendment.status}`);\n    }\n\n    amendment.votes.set(voterId, approve);\n  }\n\n  /**\n   * Resolve an amendment (check if supermajority reached)\n   */\n  resolveAmendment(amendmentId: string): Amendment {\n    const amendment = this.amendments.get(amendmentId);\n    if (!amendment) {\n      throw new Error(`Amendment not found: ${amendmentId}`);\n    }\n    if (amendment.status !== 'proposed') {\n      throw new Error(`Amendment already resolved: ${amendment.status}`);\n    }\n\n    const totalVotes = amendment.votes.size;\n    const approvals = Array.from(amendment.votes.values()).filter((v) => v).length;\n    const approvalRate = totalVotes > 0 ? approvals / totalVotes : 0;\n\n    if (approvalRate >= this.supermajorityThreshold && approvals >= amendment.requiredApprovals) {\n      amendment.status = 'approved';\n    } else {\n      amendment.status = 'rejected';\n    }\n\n    return amendment;\n  }\n\n  /**\n   * Enact an approved amendment\n   * Returns true if enacted successfully","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/guidance/src/meta-governance.ts#L391-L427","documentation":"resolveAmendment() only operates on amendments whose status is still 'proposed'. Resolution mutates status in place to 'approved' or 'rejected' (based on supermajority threshold and requiredApprovals) without removing the entry, so a second resolve call finds a non-proposed status and throws 'Amendment already resolved' with the terminal status in the message.","triggerScenarios":"Calling resolveAmendment() twice on the same ID; retry logic that re-runs resolve after a timeout even though the first call succeeded; a coordinator and a cleanup job both trying to resolve.","commonSituations":"At-least-once task redelivery without idempotency keys; polling loops that resolve on a timer; test helpers that resolve then the test body resolves again.","solutions":["Treat the Amendment return value of the first resolveAmendment() as the verdict and persist it — do not re-resolve","Make retries idempotent: check getPendingAmendments() (which only lists status 'proposed') before resolving","Catch this specific message and return the previously recorded outcome instead of failing the retry","In multi-worker setups, route resolution through a single owner"],"exampleFix":"// before\nfunction settle(id: string) {\n  return governor.resolveAmendment(id); // throws on retry\n}\n// after\nconst verdicts = new Map<string, Amendment>();\nfunction settle(id: string): Amendment {\n  if (verdicts.has(id)) return verdicts.get(id)!;\n  const pending = governor.getPendingAmendments().some(a => a.id === id);\n  const verdict = pending ? governor.resolveAmendment(id) : null;\n  if (verdict) verdicts.set(id, verdict);\n  return verdict!;\n}","handlingStrategy":"validation","validationCode":"const stillProposed = governor.getPendingAmendments().some(a => a.id === amendmentId);\nif (!stillProposed) {\n  // already approved/rejected (still in map but not pending), or terminal\n  const hist = governor.getAmendmentHistory().find(a => a.id === amendmentId);\n  return hist ?? null;\n}\nreturn governor.resolveAmendment(amendmentId);","typeGuard":"function needsResolution(governor: MetaGovernor, id: string): boolean {\n  return governor.getPendingAmendments().some(a => a.id === id); // only 'proposed' need resolving\n}","tryCatchPattern":"try {\n  governor.resolveAmendment(amendmentId);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Amendment already resolved')) {\n    return; // verdict already recorded; do not fail the retry job\n  }\n  throw err;\n}","preventionTips":["Persist the resolveAmendment() return value; it is the authoritative verdict","Give resolution a single owner in multi-worker setups to avoid double-resolve","Build retry jobs that check getPendingAmendments() before re-invoking"],"tags":["guidance","meta-governance","amendment","idempotency","invalid-state-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}