{"record":{"id":"8faffb1726509083","repo":"ruvnet/ruflo","slug":"cannot-vote-on-amendment-with-status-amendment","errorCode":null,"errorMessage":"Cannot vote on amendment with status: ${amendment.status}","messagePattern":"Cannot vote on amendment with status: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/guidance/src/meta-governance.ts","lineNumber":394,"sourceCode":"      status: 'proposed',\n      votes: new Map(),\n      ...proposal,\n    };\n\n    this.amendments.set(amendment.id, amendment);\n    return amendment;\n  }\n\n  /**\n   * Vote on an amendment\n   */\n  voteOnAmendment(amendmentId: string, voterId: string, approve: boolean): void {\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(`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;","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/guidance/src/meta-governance.ts#L376-L412","documentation":"voteOnAmendment() requires the amendment's status to still be 'proposed'. resolveAmendment() flips status to 'approved' or 'rejected' while keeping the entry in the map, so any vote arriving after resolution throws this error echoing the current status. Duplicate votes by the same voterId are NOT the trigger — the votes Map simply overwrites — this is purely a lifecycle-state violation.","triggerScenarios":"Calling voteOnAmendment() after resolveAmendment() already ran; concurrent workers where one resolves while another is still collecting votes; retry queues re-delivering a vote after the coordinator resolved.","commonSituations":"Fan-out voting where the resolver fires on quorum while stragglers vote; at-least-once message redelivery; sequential test code that resolves in a helper then votes again.","solutions":["Collect all votes before calling resolveAmendment(); resolution is the cutoff","In concurrent flows, re-check getPendingAmendments() immediately before each vote and skip non-pending IDs","Handle 'Cannot vote on amendment with status' as a benign late-vote case rather than a hard failure","Cache the Amendment returned by resolveAmendment() as the authoritative verdict instead of re-voting"],"exampleFix":"// before\nconst result = governor.resolveAmendment(id);\n// ...later, a straggler vote arrives:\ngovernor.voteOnAmendment(id, 'voter-9', true); // throws: status approved\n// after\nif (governor.getPendingAmendments().some(a => a.id === id)) {\n  governor.voteOnAmendment(id, 'voter-9', true);\n} else {\n  logger.info('Vote window closed for amendment', { id });\n}","handlingStrategy":"validation","validationCode":"if (!governor.getPendingAmendments().some(a => a.id === amendmentId)) {\n  logger.info('Vote dropped: amendment no longer proposed', { amendmentId });\n  return;\n}\ngovernor.voteOnAmendment(amendmentId, voterId, approve);","typeGuard":"function isStillProposed(governor: MetaGovernor, id: string): boolean {\n  return governor.getPendingAmendments().some(a => a.id === id);\n}","tryCatchPattern":"try {\n  governor.voteOnAmendment(amendmentId, voterId, approve);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Cannot vote on amendment with status')) {\n    return; // late vote after resolution: benign\n  }\n  throw err;\n}","preventionTips":["Resolve only after all expected voters have voted (or a timeout makes late votes droppable)","In concurrent voters, re-check getPendingAmendments() immediately before each vote","Duplicate votes by the same voterId are safe; only lifecycle state throws"],"tags":["guidance","meta-governance","amendment","voting","state-machine","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"}