Repository navigation
Proposal Workflow and Challenge System
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains the optimistic reputation proposal workflow: an oracle proposes a score, a challenge window opens, a slashing committee can reject bad proposals, and anyone can finalize once the window closes. It also documents the backup operator flow through proposeIfEmpty, the security model around bonding and scorability checks, and the finalization process that transitions a pending score into a live, matured reputation.
The system is intentionally permissionless for execution: oracles propose, the committee rejects, and anyone can finalize. This avoids liveness dependence on any single actor while keeping accountability through roles, bonds, and time-bound challenges.
At the center of this workflow is the SigvaraReputation contract. The oracle network computes scores off-chain and calls into it; consumers read matured scores to gate trust. Tests and documentation describe the intended lifecycle, attack surface, and scoring model.
graph TB
Oracle["Oracle Network<br/>Computes scores"] --> Reputation["SigvaraReputation<br/>Propose / Finalize / Reject"]
Committee["Slashing Committee<br/>Rejects bad proposals"] --> Reputation
Consumer["On-chain Consumers<br/>Trust checks"] --> Reputation
Identity["SigvaraIdentity<br/>Agent registry"] --> Reputation
StakeView["Stake View<br/>Minimum stake check"] --> Identity
Diagram sources
Section sources
- Optimistic scoring model: oracles propose scores that are not immediately live. A time-bound challenge window allows rejection before finalization.
- Roles:
-
ORACLE_ROLE: authorized to propose scores. WhenoperatorBondis set, the caller must also be an active operator in it, or the signer of one. -
SLASHING_COMMITTEE_ROLE: can reject a pending proposal during its challenge window. -
STAKING_CORE_ROLE: can zero reputation after a slash. -
UPGRADER_ROLEand admin roles manage upgrades and configuration. On mainUPGRADER_ROLEalso grantsSTAKING_CORE_ROLEandSLASHING_COMMITTEE_ROLE, whileORACLE_ROLEstays with the admin. -
GUARDIAN_ROLE(main): can pauseproposeReputationandproposeIfEmptyfor three days at a time, and nothing else.
-
- Data structures:
-
ReputationData: six-factor score plus last update timestamp. -
PendingScore: proposed data, proposal timestamp, existence flag, evidence root, the proposer, andfinalizableAt, the end of the review window fixed at proposal time.
-
- Maturity: finalized scores become spendable gradually at a configured rate per day; drops apply immediately.
Key responsibilities:
- Validate factor caps.
- Enforce agent registration, non-slashed status, and minimum stake.
- Open a challenge window on proposal, and fix its end then.
- Allow committee rejection inside the window.
- Allow permissionless finalization after the window, while the proposer could still make the proposal.
- Track evidence roots for third-party verification.
The proposer, window and pause behaviour is live on the Arc testnet proxy since 26 October 2026. A proposal made before then stores no proposer and no finalizableAt and is judged against the live challengeWindow; the two that were pending at the upgrade have finalized.
Section sources
- SigvaraReputation.sol:15-75
- SigvaraReputation.sol:141-168
- SigvaraReputation.sol:174-197
- SigvaraReputation.sol:696-752
The lifecycle flows from proposal through challenge to finalization. The oracle writes a pending score; the committee may reject it; otherwise, anyone can finalize it after the window elapses.
sequenceDiagram
participant Oracle as "Oracle"
participant Rep as "SigvaraReputation"
participant Committee as "Slashing Committee"
participant Finalizer as "Anyone"
participant Identity as "SigvaraIdentity"
Oracle->>Rep : proposeReputation(didHash, data, evidenceRoot)
Rep->>Identity : getIdentity(didHash)
Rep->>Rep : validate factors and bond checks
Rep-->>Oracle : ScoreProposed event
Note over Rep : Challenge window open
alt Committee rejects
Committee->>Rep : rejectReputation(didHash)
Rep->>Rep : verify window active
Rep-->>Committee : delete pending proposal
Rep-->>Committee : ScoreRejected event
end
alt Window expires without rejection
Finalizer->>Rep : finalizeReputation(didHash)
Rep->>Rep : re-check scorability
Rep->>Rep : check the proposer is still authorized
Rep->>Rep : write live reputation and evidence root
Rep-->>Finalizer : ReputationUpdated event
end
Diagram sources
Section sources
-
proposeReputation:- Requires
ORACLE_ROLE, and reverts while a guardian pause is running (main). - Validates each factor against its maximum.
- Checks that the agent is registered, not slashed, and bonded via the identity registry’s stake view.
- When
operatorBondis set, checks that the caller is an active operator in it, or the signer of one. - Writes a
PendingScorewith current block timestamp, evidence root, the proposer’s address andfinalizableAt. - Emits
ScoreProposed.
- Requires
- Challenge window:
- Defined by
challengeWindow(held between 1 hour and 7 days), and fixed when the proposal is made:finalizableAtisproposedAt + challengeWindowas it stood then, so a later change moves no proposal already pending. A proposal written before the field existed reads zero and is judged against the live window. - During the window, the committee may call
rejectReputation.
- Defined by
-
finalizeReputation:- Permissionless.
- Re-checks scorability (agent must still be valid).
- Ensures a pending proposal exists and
finalizableAthas passed. - Ensures the proposer could still make the proposal: it holds
ORACLE_ROLEand, with an operator set, is still an active operator in it or the signer of one. Otherwise it reverts withProposerNotAuthorized. A proposal written before the proposer was recorded readsaddress(0)and is treated as live. - Computes anchor from the previously spendable score, writes live reputation and evidence root, clears pending slot, and sets maturity anchors.
- Emits
ReputationUpdated.
flowchart TD
Start(["Start"]) --> Propose["proposeReputation()<br/>Validate role, factors,<br/>agent state, stake"]
Propose --> Pending["Write PendingScore<br/>Set proposedAt, evidenceRoot,<br/>proposer and finalizableAt"]
Pending --> Window{"Challenge window active?"}
Window --> |Yes| RejectPath["rejectReputation() allowed"]
Window --> |No| FinalizePath["finalizeReputation() allowed"]
RejectPath --> Rejected["Delete pending proposal<br/>Emit ScoreRejected"]
Rejected --> End(["End"])
FinalizePath --> CheckScorable["Re-check agent scorable"]
CheckScorable --> CheckProposer{"Proposer still authorized?"}
CheckProposer --> |Yes| WriteLive["Write live reputation<br/>Clear pending<br/>Set maturity anchors"]
CheckProposer --> |No| Stranded["Revert ProposerNotAuthorized<br/>A new proposal replaces it"]
WriteLive --> Updated["Emit ReputationUpdated"]
Updated --> End
Stranded --> End
Diagram sources
Section sources
proposeIfEmpty is designed for a checking operator covering a silent primary. It performs a compare-and-swap on the pending slot:
- If a pending proposal already exists and its proposer is still authorized, it reverts with
ScoreAlreadyPending, including the existingproposedAtso the loser can identify whose proposal won. - If the slot is empty, or the pending proposal’s proposer has lost its authority, it proceeds to
_propose, writing the backup proposal and opening a new window. A proposal whose proposer no longer holdsORACLE_ROLE, or whose operator was removed or unbonded or whose signer was cleared, cannot be finalized while its proposer lacks that authority, so it holds the slot in name only. Without this a checker covering for a removed primary would be locked out by the very removal meant to stop it. The September contract that ran on the Arc testnet until 26 October 2026 reverted for any pending proposal.
This prevents a checker from accidentally replacing a live primary proposal, which would restart the challenge window and erase divergence evidence.
flowchart TD
Start(["proposeIfEmpty Entry"]) --> ReadSlot["Read pendingScores[didHash]"]
ReadSlot --> Exists{"exists and proposer<br/>still authorized?"}
Exists --> |Yes| RevertRace["Revert ScoreAlreadyPending<br/>Include existing proposedAt"]
Exists --> |No| Propose["Call _propose()<br/>Validate and write PendingScore"]
Propose --> Event["Emit ScoreProposed"]
RevertRace --> End(["End"])
Event --> End
Diagram sources
Section sources
The slashing committee can reject a pending proposal during its challenge window:
- Requires
SLASHING_COMMITTEE_ROLE. - Verifies a pending proposal exists and the window is still active (
block.timestamp < finalizableAt, the end fixed when the proposal was made). - Deletes the pending proposal without altering the agent’s finalized score.
- Emits
ScoreRejectedwith the caller address.
A rejection needs no further ruling: the committee’s decision is the ruling, with no separate dispute step. A slash differs on main, where the committee files it and the admin, not the committee, rules if the operator disputes.
flowchart TD
Start(["rejectReputation Entry"]) --> HasPending{"pending.exists?"}
HasPending --> |No| RevertNoPending["Revert NoScorePending"]
HasPending --> |Yes| CheckWindow{"block.timestamp < finalizableAt?"}
CheckWindow --> |No| RevertExpired["Revert ChallengeWindowExpired"]
CheckWindow --> |Yes| DeletePending["Delete pendingScores[didHash]"]
DeletePending --> Emit["Emit ScoreRejected(committee)"]
Emit --> End(["End"])
RevertNoPending --> End
RevertExpired --> End
Diagram sources
Section sources
- Scorability checks:
- Agent must be registered and not slashed.
- Must hold minimum stake via the identity registry’s stake view.
- Optional bonded operator requirement:
- When
operatorBondis set, proposers must be active operators in the bonded operator set, or the signer an active operator has named. The signing key of an oracle service need not be the key that owns the bond. - Unset means the check is disabled; this is intentional for single-operator setups. On main,
setOperatorBondrequiresUPGRADER_ROLE, because passing zero switches the check off.
- When
- Proposer authority at finalization:
- The proposer is stored with the proposal and asked about again when it is finalized, live and not as a snapshot, so revoking a key takes effect at once. Removing an operator, clearing its signer or revoking
ORACLE_ROLEends whatever that key has pending, without the committee rejecting each proposal in time. - The price is that a departing operator’s pending proposals are stranded until a successor replaces them. The alternative is a removed or compromised key’s proposals going live unreviewed.
- The proposer is stored with the proposal and asked about again when it is finalized, live and not as a snapshot, so revoking a key takes effect at once. Removing an operator, clearing its signer or revoking
- Evidence roots:
- Each proposal commits to a Merkle root of evidence used to compute the score.
- Third parties can verify individual leaves against the stored root.
These checks ensure that only accountable, bonded agents receive reputation and that proposals carry verifiable evidence.
classDiagram
class SigvaraReputation {
+proposeReputation(didHash, data, evidenceRoot)
+proposeIfEmpty(didHash, data, evidenceRoot)
+finalizeReputation(didHash)
+rejectReputation(didHash)
+getTotalScore(didHash) uint8
+verifyEvidence(didHash, leaf, proof) bool
-_requireScorable(didHash)
-_requireBondedOracle()
-_proposerStillAuthorized(proposer) bool
}
class SigvaraIdentity {
+getIdentity(didHash) AgentIdentity
+stakeView() IStakeView
}
class IOperatorSet {
+isActiveOperator(address) bool
}
SigvaraReputation --> SigvaraIdentity : "reads agent state"
SigvaraReputation --> IOperatorSet : "optional bonded operator check"
Diagram sources
- SigvaraReputation.sol:427-448
- SigvaraReputation.sol:600-631
- SigvaraReputation.sol:637-656
- SigvaraReputation.sol:761-769
Section sources
- Primary proposes first; checker later attempts
proposeIfEmpty. - Expected outcome: checker reverts with
ScoreAlreadyPending, preserving the primary’s proposal and its original challenge window.
Test reference:
- Committee calls
rejectReputationwithin the challenge window. - Expected outcome: pending proposal deleted; finalized score unchanged;
ScoreRejectedemitted.
Test references:
- Anyone calls
finalizeReputationafterfinalizableAt. - Expected outcome: pending cleared, live reputation written, maturity anchors set,
ReputationUpdatedemitted.
Test references:
- The operator that made a pending proposal is removed (or its signer cleared, or its
ORACLE_ROLErevoked) before the proposal is finalized. - Expected outcome:
finalizeReputationreverts withProposerNotAuthorizedand the score is not made live. A checking operator replaces the proposal withproposeIfEmptyat once, and a primary or another authorized operator replaces it withproposeReputation. The committee did not have to reject it inside its window.
Test references:
- SigvaraReputation.t.sol:1429-1445
- SigvaraReputation.t.sol:1487-1499
- SigvaraReputation.t.sol:1517-1533
- SigvaraReputation.t.sol:1569-1590
Section sources
The optimistic model trades immediate finality for a bounded challenge period. Oracles propose; the community and committee can contest; finalizers execute once safe. Maturity ensures trust cannot be spent instantly, reducing farm-and-cash-out incentives.
stateDiagram-v2
[*] --> Proposed
Proposed --> Rejected : "committee rejects"
Proposed --> Finalized : "window expires"
Rejected --> Proposed : "new proposal"
Finalized --> [*]
The reputation contract depends on:
- Access control roles for proposing, rejecting, and zeroing.
-
TimelockedGrantsandInflowGuardon main: the first lets the admin revoke upgrader-administered roles at once, the second adds the guardian’s pause on proposals. - Identity registry for agent state and stake view.
- Optional bonded operator registry for proposer eligibility, asked again at finalization about the proposer.
- Merkle proof utilities for evidence verification.
graph LR
Rep["SigvaraReputation"] --> AC["AccessControlUpgradeable"]
Rep --> UUPS["UUPSUpgradeable"]
Rep --> Identity["SigvaraIdentity"]
Rep --> Bond["IOperatorSet"]
Rep --> Merkle["MerkleProof"]
Diagram sources
- SigvaraReputation.sol:4-11
- SigvaraReputation.sol:12
- SigvaraReputation.sol:174-197
- SigvaraReputation.sol:761-769
Section sources
- SigvaraReputation.sol:4-11
- SigvaraReputation.sol:12
- SigvaraReputation.sol:174-197
- SigvaraReputation.sol:761-769
- Factor validation occurs at proposal time; finalization reads and writes minimal storage.
- Maturity accrues on read, allowing growth even if oracles stop proposing.
- Evidence verification uses Merkle proofs, enabling efficient third-party audits without trusting oracle files.
Common issues and their causes:
-
ScoreAlreadyPending:- A checker attempted to cover when a primary proposal was already live.
- Action: inspect the returned
proposedAtto identify the winning proposal; do not retry blindly.
-
ChallengeWindowActive:- Finalization attempted too early.
- Action: wait until
finalizableAt, which the error carries.
-
ChallengeWindowExpired:- Committee tried to reject after the window closed.
- Action: use finalization instead; rejection is no longer possible.
-
NoScorePending:- Finalization or rejection called without a pending proposal.
- Action: ensure a proposal exists and has not been rejected or finalized.
-
AgentNotRegistered/AgentSlashed/AgentNotBonded:- Scorability checks failed.
- Action: confirm agent registration, non-slashed status, and minimum stake.
-
OracleNotBonded:- Proposer not an active bonded operator (or the signer of one) when
operatorBondis set. - Action: ensure the oracle is admitted and bonded in the operator set, or that its operator has named it with
setSigner.
- Proposer not an active bonded operator (or the signer of one) when
-
ProposerNotAuthorized:- Finalization was attempted on a proposal whose proposer has lost
ORACLE_ROLE, or is no longer an active operator or signer. - Action: do not retry; the proposal cannot be finalized while its proposer lacks that authority. A proposal from an authorized proposer replaces it.
- Finalization was attempted on a proposal whose proposer has lost
-
InflowsPausedUntil:- A guardian pause is running.
- Action: wait for the time in the error, or for the guardian or admin to lift it. Finalizing and rejecting proposals already made is not paused.
-
ChallengeWindowTooShort/ChallengeWindowTooLong/MaturityRateTooHigh:- The admin set a value outside its bounds: a window between 1 hour and 7 days, a maturity rate above 0 and at most 100 points a day.
- Action: pick a value inside the bounds.
Operational notes:
- Oracle index logs distinguish between losing a race (
isSlotTaken) and other errors, helping operators avoid false alarms. - The primary treats
ProposerNotAuthorizedon finalize as a stranded proposal to replace, not as a failure. A checking operator asks the contract, with a dry run ofproposeIfEmpty, whether an occupied slot is stranded, and covers for it if so; a question it cannot ask is answered “alive”. - Chain helpers expose
getPendingScoreandgetChallengeWindowfor off-chain monitoring.
Section sources
- SigvaraReputation.sol:239-260
- SigvaraReputation.sol:408-422
- SigvaraReputation.sol:454-504
- SigvaraReputation.sol:513-523
- SigvaraReputation.sol:600-631
- index.js:1091-1130
- chain.test.js:198-223
The proposal workflow centers on an optimistic scoring model with a bounded challenge window. Oracles propose scores backed by evidence; the slashing committee can reject bad proposals; and anyone can finalize once the window closes. proposeIfEmpty enables backup operators to cover gaps safely using compare-and-swap semantics, and it may replace a proposal whose proposer has lost its authority. Finalization stays permissionless, but a proposal is made live only while its proposer could still make it. Scorability and bonding checks ensure only accountable agents receive reputation, while maturity prevents instant spending of newly earned trust. Together, these mechanisms balance liveness, security, and economic accountability.
- Agent Registration and Lifecycle
- Contract Overview and Architecture
- Economic Security Model and Incentives
- Evidence Verification and Merkle Proofs
- Key Management and Rotation
- Maturity Mechanism and Score Release
- Proposal Workflow and Challenge System
- Score Storage and Data Model
- SigvaraIdentity Contract
- SigvaraOracleBond Contract
- SigvaraReputation Contract
- SigvaraStaking Contract
- Slashing Lifecycle and Dispute Resolution
- Stake Management Functions
- Status Management and Control
- Token and Fee Contracts
- Unbonding Period and Withdrawal Security
- Anti-Manipulation and Security Measures
- Community Flag Processing
- Evidence Sources and Verification
- External Trust Integration
- Merkle Proof System
- Oracle Score Computation
- Payment-Backed Attestations
- Scoring Model and Formulas
- Agent Creation and Key Management
- Agent Operations
- Agent Registration Workflow
- Challenge-Response Authentication
- Gate Access Control
- Integration Patterns and Examples
- SDK Overview and Setup
- Verifier Functions