← Changelog

Governance timelock + dispute resolution

v0.3.0 · March 14, 2026 · Contract

Two changes that turn the contract from working into operationally credible: parameter changes can no longer be unilateral, and QA decisions are no longer terminal. Both shipped in the same release because they answer the same governance question — what stops the trusted role from acting in bad faith?

Governance timelock

Before v0.3.0, the OEM could rotate keys, change pricing, swap the ERS (Engineering Requirement Spec) reference, or upgrade the contract instantly. That was fine while the contract was a single-tenant prototype. It is not fine for a settlement layer that handles factory payouts. v0.3.0 routes those mutations through pending-update queues with enforced delays.

New types

StructPurpose
PendingERSUpdateQueues an ERS hash change with a configurable delay (min 1h)
PendingPricingUpdateReserved for cloud/customer-hosted fee schedule changes
PendingRolesUpdateReserved for OEM/Factory/QA address rotation
PendingUpgradeUpdateReserved for contract code upgrades (Wasm hash)
PendingFeeRateUpdateHard-coded 30-day delay; the longest timelock in the system

Constants

const MIN_TIMELOCK_DELAY: u64 = 3600;          // 1 hour floor
const FEE_RATE_TIMELOCK_DELAY: u64 = 86400 * 30; // 30 days for fee changes
const DEFAULT_CLOUD_FEE_BPS: u32 = 75;          // 0.75%
const DEFAULT_CUSTOMER_FEE_BPS: u32 = 50;       // 0.50%

Flow

Three-step proposal pattern, applied uniformly to fee-rate and ERS changes that shipped in this release:

1. propose_fee_rate(caller, new_bps)
     → queues a PendingFeeRateUpdate, eta = now + 30 days

2. approve_fee_rate(caller)
     → second-signer approval (multi-sig discipline)

3. execute_fee_rate(caller)
     → reverts if now < eta; otherwise applies the new rate

The 30-day fee-rate delay is intentional: factories quoting bids today need to know the fee they'll be paying when the order settles. Shorter delays would let an OEM front-run a known queue of pending settlements. ERS updates use the 1-hour floor because they're caller-supplied per-proposal — a factory that disagrees with a hot-swap can simply not accept the next service order during the delay window.

Dispute resolution

v0.2.0 added QA staking. v0.3.0 adds the path that actually puts a stake at risk: an OEM (or any caller paying the fee) can challenge a QA decision and trigger a re-inspection. If the re-inspection confirms the challenge, a portion of the QA's stake is slashed.

New functions

FunctionCallerWhat it does
request_reinspectOEM (or sample owner)Pays the challenge fee, locks the lot, sets due_by for QA response
qa_reinspect_respondQASubmits revised counts within due_by; late responses revert
challenge_default_slashOEMIf QA fails to respond by due_by, slashes up to MAX_SLASH_BPS

Constants

const CHALLENGE_COST_BPS: u32 = 10;        // 0.1% of lot value
const MAX_SLASH_BPS: u32 = 2000;           // 20% cap on a single slash
const MAX_CHALLENGE_SAMPLE: u32 = 100;     // re-sample size cap

The numbers are deliberately asymmetric. CHALLENGE_COST_BPS = 10 (0.1%) makes challenging cheap enough that a legitimately wronged OEM doesn't think twice; MAX_SLASH_BPS = 2000 (20%) caps the punishment so a single dispute can't bankrupt a QA running parallel jobs. Stakes are designed to absorb 5+ disputes before requiring re-collateralization — that's the operational floor the staking parameters were tuned against.

Slashing distribution

Inherited from v0.2.0: 50% of the slash goes to the challenger (refunding their fee plus an integrity bounty), 50% goes to the OEM (compensating for the disputed lot). The contract holds nothing — the platform doesn't profit from disputes.

Why these two together

Governance timelock and dispute resolution are the same primitive applied at two layers: delay before irreversibility. Parameter changes get a timelock. QA decisions get a re-inspection window. Both narrow the surface where a single trusted party can finalize an outcome before anyone else can react.

The lot-based pipeline (create_lotqa_commit*execute_payment) is unchanged. Existing lots continue to settle on the original path; new lots opt in to the dispute window simply by virtue of a non-zero QA stake being present.

Compatibility

Additive on top of v0.2.0. The new timelock applies prospectively — parameters set at init and never proposed-against retain their original behavior. Test suite grew to 47 passing (39 from v0.2.0 baseline + 8 new dispute/timelock tests), zero regressions.

Known follow-ups (closed in later releases)

  • qa_reinspect_respond initially accepted late responses → closed in v0.4.1 with a now > due_by guard that reverts.
  • challenge_default_slash initially allowed QA to slash itself, and accepted slash_bps == 0 as a no-op → both closed in v0.4.1 (caller-must-not-be-QA + non-zero param check).
  • PendingPricingUpdate / PendingRolesUpdate / PendingUpgradeUpdate defined as types in v0.3.0; full propose/execute paths remain reserved for a later release.

References

Source: github.com/fairb-dev/Fairfoundry. Architecture diagrams + the full contract API table are on the platform page. The dispute hardening that closed the v0.3.0 follow-ups is described in v0.4.0 / v0.4.1.

Want to read the contract?

Two contracts, ~3000 lines of Rust, 61 tests, CC BY-NC 4.0.