{"record":{"id":"6686f175a56c1d1c","repo":"ruvnet/ruflo","slug":"only-primary-can-propose-values","errorCode":null,"errorMessage":"Only primary can propose values","messagePattern":"Only primary can propose values","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/swarm/src/consensus/byzantine.ts","lineNumber":184,"sourceCode":"  electPrimary(): string {\n    const nodeIds = [this.node.id, ...Array.from(this.nodes.keys())];\n    const primaryIndex = this.node.viewNumber % nodeIds.length;\n    const primaryId = nodeIds[primaryIndex];\n\n    this.node.isPrimary = primaryId === this.node.id;\n\n    for (const [id, node] of this.nodes) {\n      node.isPrimary = id === primaryId;\n    }\n\n    this.emit('primary.elected', { primaryId, viewNumber: this.node.viewNumber });\n\n    return primaryId;\n  }\n\n  async propose(value: unknown): Promise<ConsensusProposal> {\n    if (!this.node.isPrimary) {\n      throw new Error('Only primary can propose values');\n    }\n\n    this.proposalCounter++;\n    const sequenceNumber = ++this.node.sequenceNumber;\n    const digest = this.computeDigest(value);\n    const proposalId = `bft_${this.node.viewNumber}_${sequenceNumber}`;\n\n    const proposal: ConsensusProposal = {\n      id: proposalId,\n      proposerId: this.node.id,\n      value,\n      term: this.node.viewNumber,\n      timestamp: new Date(),\n      votes: new Map(),\n      status: 'pending',\n    };\n\n    this.proposals.set(proposalId, proposal);","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/swarm/src/consensus/byzantine.ts#L166-L202","documentation":"ByzantineConsensus is primary-based (PBFT-style): only the node currently elected primary may propose values. electPrimary() sets node.isPrimary on exactly one node and emits 'primary.elected'; propose() on any other node throws before a proposal is created. The ConsensusEngine facade exposes this via isLeader(), which delegates to isPrimary() for the byzantine implementation.","triggerScenarios":"Calling propose() from a follower node; proposing before electPrimary()/initial election has run; proposing after a view change where this node lost primary status; symmetric deployments where every replica unconditionally runs the same propose code regardless of role.","commonSituations":"Fleet-wide code proposing on a timer from every node; view-change races during primary failure; single-node tests that never ran an election; proposals routed to the wrong node after membership churn.","solutions":["Propose only from the primary: subscribe to 'primary.elected' (or check engine.isLeader()) and route proposals accordingly.","Forward the value to the current primary over the transport when this node is a follower.","Ensure election/view setup has completed before the first propose.","On view change, re-check primary status and retry the proposal."],"exampleFix":"// before — every replica proposes\nawait byzantine.propose(value); // followers throw\n\n// after — only the current primary proposes\nlet currentPrimary: string | null = null;\nbyzantine.on('primary.elected', ({ primaryId }) => { currentPrimary = primaryId; });\nif (currentPrimary === thisNodeId) {\n  await byzantine.propose(value);\n} else if (currentPrimary) {\n  await transport.send(currentPrimary, { type: 'propose', value });\n}","handlingStrategy":"validation","validationCode":"// ConsensusEngine.isLeader() delegates to isPrimary() for the byzantine implementation\nif (!engine.isLeader()) {\n  throw new Error('not primary; forward the proposal to the current primary');\n}\nawait engine.propose(value);","typeGuard":"function canPropose(engine: ConsensusEngine): boolean {\n  return engine.isLeader(); // true only for the byzantine primary / raft leader\n}","tryCatchPattern":"try {\n  await byzantine.propose(value);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Only primary can propose values') {\n    // wait for the next 'primary.elected' event, or forward the value to the primary\n  } else {\n    throw e;\n  }\n}","preventionTips":["Route all proposals through a primary-aware dispatcher; never propose from every replica.","Cache the current primary from the 'primary.elected' event and re-check before each proposal.","Expect view changes: on leadership loss, re-check and retry or forward."],"tags":["consensus","byzantine","pbft","leader-election"],"backgroundTag":"not-cluster-leader","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"}