# Vaultfire Codex — Batch 6: Governance, Privacy, and Miscellaneous Contracts

> Comprehensive line-by-line technical reference for 26 unique contracts covering multisig governance, anti-surveillance enforcement, privacy guarantees, mission enforcement, task escrow, dispute resolution, capability credentials, reputation staking, trust oracle, forum registry, bond insurance, cross-chain trust portability, and supporting utilities.

---

## Table of Contents

1. [MultisigGovernance.sol](#1-multisigsol--contractsmultisiggovernancesol)
2. [MultisigGovernanceV3 (test_fixes)](#2-multisiggovernancesol--test_fixescontractsmultisiggovernancesol)
3. [MultisigGovernanceV3 (vf-audit-contracts)](#3-multisiggovernancesol--vf-audit-contractsmultisiggovernancesol)
4. [MultisigGovernanceV3 (v3-pr-ready)](#4-multisiggovernancesol--v3-pr-readycontractsv3multisiggovernancesol)
5. [push\_contracts\_v3\_MultisigGovernanceV3.sol](#5-push_contracts_v3_multisiggovernancesol)
6. [AntiSurveillance.sol](#6-antisurveillancesol--contractsantisurveillancesol)
7. [PrivacyGuarantees.sol](#7-privacyguaranteessol--contractsprivacyguaranteessol)
8. [MissionEnforcement.sol](#8-missionenforcementsol--contractsmissionenforcementsol)
9. [VaultfireTaskEscrow.sol](#9-vaultfiretaskescrowsol)
10. [VaultfireDisputeResolution.sol](#10-vaultfiredisputeresolutionsol)
11. [VaultfireCapabilityCredentials.sol](#11-vaultfirecapabilitycredentialssol)
12. [VaultfireReputationStaking.sol](#12-vaultfirereputationstakingsol)
13. [VaultfireTrustOracle.sol](#13-vaultfiretrustoracle-sol)
14. [VaultfireForumRegistry.sol](#14-vaultfireforumregistrysol)
15. [VaultfireBondInsurancePool.sol](#15-vaultfirebondinsurancepoolsol)
16. [TrustPortabilityExtensionV3 (push)](#16-trustportabilityextensionv3-push-contractspush_contracts_v3_trustportabilityextensionv3sol)
17. [TrustPortabilityExtensionV3 (workspace root)](#17-trustportabilityextensionv3-workspace-root)
18. [TrustDataBridge.sol](#18-trustdatabridgesol)
19. [IERC20Minimal (vf-audit-contracts)](#19-ierc20minimal--vf-audit-contractsierc20minimalsol)
20. [IERC20Minimal (v3-pr-ready)](#20-ierc20minimal--v3-pr-readycontractsv3ierc20minimalsol)
21. [VaultfireErrors.sol (vf-audit-contracts)](#21-vaultfireerrorssol--vf-audit-contractsvaultfireerrorssol)
22. [VaultfireErrors.sol (test_fixes)](#22-vaultfireerrorssol--test_fixescontractsvaultfireerrorssol)
23. [IVaultfireBridgeNotifier.sol](#23-ivaultfirebridgenotifiersol)
24. [VaultfireInterface.sol](#24-vaultfireinterfacesol)
25. [Deploy.s.sol](#25-deployssol--theloopbreaker-siterisc0scriptdeployssol)
26. [SubmitProof.s.sol](#26-submitproofssol--theloopbreaker-siterisc0scriptsubmitproofssol)
27. [consent\_contract.sol](#27-consent_contractsol--theloopbreaker-siteconsent_contractsol)
28. [RewardStream.t.sol](#28-rewardstreamtsol--theloopbreaker-sitetestrewardstreamtsol)

---

## 1. MultisigGovernance — `contracts/MultisigGovernance.sol`

**Category:** Multisig governance (V1 baseline)

### Full Source

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

// [357 lines — see workspace/contracts/MultisigGovernance.sol]
```

```
Line   1  // SPDX-License-Identifier: MIT
Line   2  pragma solidity ^0.8.25;
Line   4  /**
Line   5   * @title MultisigGovernance
Line   6   * @notice OpenZeppelin-style multisig governance for critical owner functions.
...
Line  21  contract MultisigGovernance {
Line  23  // ============ Errors ============
Line  25  error NotSigner();
Line  26  error ZeroAddress();
Line  27  error InvalidThreshold();
Line  28  error DuplicateSigner();
Line  29  error SignerNotFound();
Line  30  error TransactionNotFound();
Line  31  error TransactionAlreadyExecuted();
Line  32  error TransactionExpired();
Line  33  error AlreadyConfirmed();
Line  34  error NotConfirmed();
Line  35  error ThresholdNotMet();
Line  36  error ExecutionFailed();
Line  37  error CannotRemoveLastSigner();
Line  38  error ThresholdExceedsSigners();
Line  40  // ============ Events ============
Line  72  // ============ Structs ============
Line  81  struct Transaction { address target; uint256 value; bytes data; bool executed;
         uint256 confirmations; uint256 proposedAt; uint256 expiresAt; }
Line  84  // ============ Constants ============
Line  85  uint256 public constant TRANSACTION_EXPIRY = 7 days;
Line  87  // ============ State Variables ============
Line  90  address[] public signers;
Line  93  mapping(address => bool) public isSigner;
Line  96  uint256 public threshold;
Line  99  mapping(uint256 => Transaction) public transactions;
Line 102  mapping(uint256 => mapping(address => bool)) public confirmations;
Line 105  uint256 public transactionCount;
Line 107-156  // ============ Modifiers ============ / constructor
Line 158-239  // ============ Transaction Lifecycle ============
Line 241-288  // ============ Signer Management (via multisig only) ============
Line 290-355  // ============ View Functions ============ + _confirm internal
```

### Line-by-Line Walkthrough

**Lines 1–20 — License, pragma, NatSpec.** MIT license, Solidity 0.8.25. NatSpec describes this as an "OpenZeppelin-style" M-of-N multisig born from a 2026 professional security audit recommendation to replace single-owner control.

**Lines 25–38 — Custom errors.** Fourteen custom errors replace revert strings. All are 4-byte selectors only, eliminating ABI-encoded string payloads from reverts. `CannotRemoveLastSigner` and `ThresholdExceedsSigners` protect the minimum viable signer set.

**Lines 42–74 — Events.** `TransactionProposed` logs the full calldata payload, target, ETH value, and expiry. `TransactionConfirmed` / `ConfirmationRevoked` track individual signer votes. `TransactionExecuted` records the executor. `SignerAdded` / `SignerRemoved` / `ThresholdChanged` audit the signer set.

**Lines 81–88 — Transaction struct.** Packs target, value, calldata blob, execution flag, cumulative confirmation count, proposal timestamp, and expiry into a single storage record keyed by sequential `txId`.

**Line 85 — TRANSACTION_EXPIRY constant.** Fixed at 7 days. Any proposal that accumulates enough signatures but is not executed within 7 days of proposal reverts on `executeTransaction`.

**Lines 90–105 — State variables.** `signers` is a dynamic array for enumeration. `isSigner` map provides O(1) membership lookup without iterating the array. `threshold` is the M in M-of-N. `transactions` maps IDs to structs. `confirmations` is a two-dimensional map: `txId → signer → bool`, preventing double-counting. `transactionCount` is the monotonically incrementing ID counter.

**Lines 109–132 — Modifiers.** `onlySigner` reverts with `NotSigner()` if `isSigner[msg.sender]` is false. `onlySelf` requires `msg.sender == address(this)` — the only way to reach it is via a transaction that the multisig itself executes. `txExists` bounds-checks against `transactionCount`. `notExecuted` rejects replay of already-run transactions. `notExpired` compares `block.timestamp > expiresAt`.

**Lines 139–156 — Constructor.** Validates that at least one signer is provided and that `threshold ∈ [1, len(signers)]`. Iterates signers, rejecting zero-address or duplicates, registers each into `isSigner` and `signers`, emits `SignerAdded` for each.

**Lines 165–191 — proposeTransaction.** Any signer may propose. Increments `transactionCount` (post-increment, so first txId = 0). Writes the `Transaction` struct. Auto-calls `_confirm` so the proposer's own signature is counted immediately — proposal and first confirmation are atomic. Note: this version does **not** track a `pendingTxIds` list (a gap corrected in V3).

**Lines 195–203 — confirmTransaction.** Delegates entirely to `_confirm` after modifier guards. `_confirm` enforces uniqueness via `confirmations[txId][msg.sender]`.

**Lines 207–219 — revokeConfirmation.** Notably **lacks** `notExpired` guard in V1 — a signer could revoke after expiry, though this has no practical effect since execution also checks expiry. Fixed in V3 (see `MultisigGovernanceV3`).

**Lines 223–239 — executeTransaction.** Checks threshold, marks `executed = true` before the external call (CEI pattern), then calls `txn.target.call{value: txn.value}(txn.data)`. Uses `revert ExecutionFailed()` on failure.

**Lines 245–276 — addSigner / removeSigner.** Both are `onlySelf` — they can only be reached through a multisig-approved transaction targeting `address(this)`. `removeSigner` protects against leaving zero signers (`CannotRemoveLastSigner`) and against creating a threshold that exceeds the new signer count (`ThresholdExceedsSigners`). Uses swap-and-pop for gas-efficient array removal. **Ghost-confirmation vulnerability:** removed signer's confirmations on pending transactions are **not** purged in V1 (patched in V3 via `pendingTxIds` sweep).

**Lines 281–288 — changeThreshold.** `onlySelf`. Allows any threshold from 1 to `signers.length`. V1 allows threshold = 1 — a known weakness addressed by `MINIMUM_THRESHOLD = 2` in V3.

**Lines 344–356 — _confirm / receive.** Internal helper sets `confirmations[txId][msg.sender] = true`, increments counter, emits `TransactionConfirmed`. The `receive()` fallback accepts ETH silently to allow the multisig to hold and forward ETH in proposals.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `signers` | `address[]` | Enumerable signer array | Mutable via `addSigner`/`removeSigner` (self-only) |
| `isSigner` | `mapping(address→bool)` | O(1) signer membership | Mutable via signer management |
| `threshold` | `uint256` | Required confirmations (M) | Mutable via `changeThreshold` (self-only) |
| `transactions` | `mapping(uint256→Transaction)` | All proposals | Append-only writes (executed flag flipped on run) |
| `confirmations` | `mapping(uint256→mapping(address→bool))` | Per-signer confirmation flags | Set true on confirm, false on revoke |
| `transactionCount` | `uint256` | Monotonic ID counter | Incremented on each proposal |

### Functions — Full Behavioral Spec

**`proposeTransaction(target, value, data) → txId`**
- Access: `onlySigner`
- Reverts: `ZeroAddress` if target is address(0)
- Effect: creates `Transaction` record, auto-confirms for proposer
- Returns: sequential `txId` starting at 0

**`confirmTransaction(txId)`**
- Access: `onlySigner`
- Guards: `txExists`, `notExecuted`, `notExpired`
- Effect: sets confirmation flag, increments count, emits `TransactionConfirmed`
- Reverts: `AlreadyConfirmed` if already signed

**`revokeConfirmation(txId)`**
- Access: `onlySigner`
- Guards: `txExists`, `notExecuted` (but NOT `notExpired` in V1)
- Effect: clears flag, decrements count, emits `ConfirmationRevoked`
- Reverts: `NotConfirmed` if never confirmed

**`executeTransaction(txId)`**
- Access: `onlySigner`
- Guards: `txExists`, `notExecuted`, `notExpired`
- Effect: marks executed, performs external call, reverts on call failure
- CEI: `executed = true` set before `.call`

**`addSigner(signer)`**
- Access: `onlySelf` (only reachable via multisig proposal)
- Reverts: `ZeroAddress`, `DuplicateSigner`
- Effect: adds to `signers` array and `isSigner` map

**`removeSigner(signer)`**
- Access: `onlySelf`
- Reverts: `SignerNotFound`, `CannotRemoveLastSigner`, `ThresholdExceedsSigners`
- Effect: swap-and-pop from `signers`, clears `isSigner` — does NOT purge ghost confirmations

**`changeThreshold(newThreshold)`**
- Access: `onlySelf`
- Reverts: `InvalidThreshold` if 0 or > `signers.length`
- Effect: updates `threshold`, emits `ThresholdChanged`

### Math

**Confirmation threshold check:** `confirmations >= threshold`

No BPS scaling. Threshold is an absolute count. Execution requires `signatures_count >= threshold`.

### Events

| Event | Trigger | Data Logged |
|---|---|---|
| `TransactionProposed` | `proposeTransaction` | txId, proposer, target, data, value, expiresAt |
| `TransactionConfirmed` | `_confirm` (via propose or confirm) | txId, signer |
| `ConfirmationRevoked` | `revokeConfirmation` | txId, signer |
| `TransactionExecuted` | `executeTransaction` | txId, executor |
| `SignerAdded` | `addSigner` + constructor | signer address |
| `SignerRemoved` | `removeSigner` | signer address |
| `ThresholdChanged` | `changeThreshold` | old and new threshold |

### Security Invariants

1. `threshold ≥ 1` at all times (enforced by constructor + `changeThreshold`)
2. `signers.length ≥ 1` always (enforced by `CannotRemoveLastSigner`)
3. `threshold ≤ signers.length` always
4. Executed transactions cannot be re-executed (`notExecuted` guard)
5. Expired transactions cannot be executed (`notExpired` guard)
6. Double-confirming is prevented (`AlreadyConfirmed`)
7. **Known gap:** ghost confirmations from removed signers persist (patched in V3)
8. **Known gap:** `revokeConfirmation` missing `notExpired` guard (patched in V3)
9. **Known gap:** threshold = 1 allowed (patched in V3 with `MINIMUM_THRESHOLD = 2`)
10. **Known gap:** no cap on signer count (patched in V3 with `MAX_SIGNERS = 20`)

### Integration with Vaultfire

`MultisigGovernance` is the foundational governance contract. `VaultfireTaskEscrow` stores its address as the immutable `governance` and only allows `resolveDispute` to be called by this address. Bond contracts' `onlyOwner` functions are intended to be owned by a deployed multisig instance. This V1 contracts is superseded by `MultisigGovernanceV3` but remains deployed as the baseline reference.

### Dependencies + Rebuild Recipe

- **Dependencies:** None — zero imports
- **Solidity:** `^0.8.25`
- **Deploy:** `constructor(address[] _signers, uint256 _threshold)`
- **Test suite file:** No dedicated test file in this batch (governance tests live in `AuditRemediation.test.js`)

---

## 2. MultisigGovernanceV3 — `test_fixes/contracts/MultisigGovernanceV3.sol`

**Category:** Multisig governance (V3 timelock variant with guardians)

### Overview

This is the test-fixes branch of the V3 multisig. It introduces a fundamentally different architecture from V1: a **timelock model** rather than a simple threshold-counter. Proposals must wait `EXECUTION_TIMELOCK = 24 hours` before execution regardless of signature count. The guardian system adds a 3-of-3 emergency bypass. Signer rotation is governance-gated via a separate `SignerChangeRequest` flow with its own 7-day cooldown.

### Full Source (line-by-line)

```
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 4  import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
 6–9   NatSpec: V3 — threshold enforcement, time-locked execution, signer rotation limits, expiry
10  contract MultisigGovernanceV3 is ReentrancyGuard {
12  uint256 public constant EXECUTION_TIMELOCK     = 24 hours;
13  uint256 public constant PROPOSAL_EXPIRY        = 7 days;
14  uint256 public constant SIGNER_CHANGE_COOLDOWN = 7 days;
15  uint256 public constant MIN_THRESHOLD_BPS      = 5000; // 50%
17–30  Custom errors (13 total)
32–41  struct Proposal { bytes data; address target; uint256 value; uint64 createdAt;
        uint64 executableAt; uint256 signatures; bool executed; uint256 nonce; }
43  mapping(uint256 => Proposal)                     public proposals;
44  mapping(uint256 => mapping(address => bool))     public hasSigned;
45  mapping(address => bool)                         public isSigner;
46  address[3]                                       public guardians;
47  address[]                                        public signers;
48  uint256                                          public threshold;
49  uint256                                          public proposalCount;
50  uint256                                          public globalNonce;
51  uint256                                          public lastSignerChange;
54–55  mapping(bytes32 => mapping(address => bool)) public guardianApprovals;
       mapping(bytes32 => uint256)                  public guardianApprovalCount;
57–64  Events (ProposalCreated, ProposalSigned, ProposalExecuted, SignerAdded,
        SignerRemoved, ThresholdUpdated, EmergencyApproved, EmergencyExecuted)
66–78  constructor: validates signers, threshold ≥ 1, MIN_THRESHOLD_BPS, stores guardians
80–83  modifier onlySigner
85–102 createProposal: assigns executableAt = now + EXECUTION_TIMELOCK, nonce = ++globalNonce
104–112 signProposal: nonReentrant, checks expiry, no double-sign, increments p.signatures
114–126 executeProposal: nonReentrant, checks timelock, expiry, threshold, CEI flag, .call
128    error SignerChangeRequiresThreshold();
130–136 struct SignerChangeRequest { address add; address remove; uint64 proposedAt;
         uint256 approvals; bool executed; }
138–140 mappings for signerChanges, signerChangeApprovals, signerChangeCount
142–144 events SignerChangeProposed, SignerChangeApproved, SignerChangeExecuted
148–166 proposeSignerChange: cooldown gate, duplicate/zero checks, starts approval at 1
168–177 approveSignerChange: checks expiry, no double-approval
179–206 executeSignerChange: requires threshold approvals + EXECUTION_TIMELOCK + expiry
         swap-and-pop for removal, then push new signer, sets lastSignerChange
208–228 emergencyExecute: all-3-guardian check, callHash deduplication, execute
230–232 getProposalStatus: view returns full Proposal struct
234    receive() external payable
```

### Line-by-Line Walkthrough

**Line 4 — OZ ReentrancyGuard.** Imported from OpenZeppelin. Provides `nonReentrant` modifier via a slot-based lock (`_status`).

**Lines 12–15 — Constants.**
- `EXECUTION_TIMELOCK = 24 hours` — every proposal must wait at minimum 24 hours after creation before `executeProposal` can run. Gives signers a window to revoke if a bad proposal slips through.
- `PROPOSAL_EXPIRY = 7 days` — proposals that are not executed within 7 days become permanently invalid.
- `SIGNER_CHANGE_COOLDOWN = 7 days` — prevents rapid successive signer rotations.
- `MIN_THRESHOLD_BPS = 5000` — threshold must be ≥ 50% of the signer count at all times.

**Lines 17–30 — Errors.** Thirteen custom errors. `SignerChangeCooldown`, `NotGuardian`, `GuardianAlreadyApproved` are new versus V1. `AddressAlreadySigner` is reused (confusingly) to indicate a zero address was passed to `proposeSignerChange` — a minor naming ambiguity.

**Lines 32–41 — Proposal struct.** Uses `uint64` for `createdAt` and `executableAt` to pack timestamps into 8 bytes each, saving one storage slot versus two `uint256`s. The `nonce` field provides global sequencing for off-chain tooling.

**Line 46 — guardians[3].** Fixed-size array of exactly 3 guardian addresses. The emergency execute path requires all three to sign off.

**Lines 66–78 — Constructor.** Enforces `_threshold * 10000 / _signers.length >= MIN_THRESHOLD_BPS` — e.g., with 4 signers the threshold must be ≥ 2. Does not check for duplicate signers in the input array (a minor gap).

**Lines 85–102 — createProposal.** `onlySigner`. `executableAt = block.timestamp + EXECUTION_TIMELOCK`. Assigns `nonce = ++globalNonce` for off-chain ordering. Returns `proposalId` (starts at 1 due to `++proposalCount`).

**Lines 104–112 — signProposal.** `nonReentrant`. Checks `createdAt != 0` (existence), `createdAt + PROPOSAL_EXPIRY > now` (not expired), `!hasSigned[id][msg.sender]` (no double-sign). Increments `p.signatures`.

**Lines 114–126 — executeProposal.** `nonReentrant`. Checks `!p.executed`, `now >= p.executableAt` (timelock), `now <= createdAt + PROPOSAL_EXPIRY` (not expired), `p.signatures >= threshold`. Sets `p.executed = true` **before** the `.call` (CEI). Note: this variant does NOT have the MED-21 post-execution threshold re-check that the audit-contracts variant adds.

**Lines 148–166 — proposeSignerChange.** Guards against the `SIGNER_CHANGE_COOLDOWN`. The `add == address(0)` revert uses `AddressAlreadySigner` (semantically imprecise but functionally correct). Starts with `approvals = 1` from the proposer.

**Lines 179–206 — executeSignerChange.** Requires `sc.approvals >= threshold`. Also enforces `EXECUTION_TIMELOCK` on signer changes. This means a rotation always takes at least 24 hours to execute after proposal, in addition to requiring threshold approvals.

**Lines 208–228 — emergencyExecute.** Iterates `guardians[3]` to verify caller. Uses `keccak256(abi.encode(target, data))` as the `callHash` to identify the proposed action. Each guardian independently calls `emergencyExecute`; on the third approval, the call fires. **Critical note:** this variant does NOT have the CRIT-01 fix (`executedCallHashes`) — meaning after all 3 guardians approve, `guardianApprovalCount` stays at 3 permanently and any guardian can re-trigger execution. This is the unpatched version.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `proposals` | `mapping(uint256→Proposal)` | All proposal records | Written on create, `executed` flag on run |
| `hasSigned` | `mapping(uint256→mapping(address→bool))` | Per-signer sign flags | Set true on `signProposal` |
| `isSigner` | `mapping(address→bool)` | Fast membership check | Changed by `executeSignerChange` |
| `guardians` | `address[3]` | Fixed 3-element guardian array | Set in constructor, immutable thereafter |
| `signers` | `address[]` | Enumerable signer list | Changed by `executeSignerChange` |
| `threshold` | `uint256` | Required signatures | Not externally settable post-deployment in this version |
| `proposalCount` | `uint256` | Next proposal ID (1-based) | Incremented on each `createProposal` |
| `globalNonce` | `uint256` | Monotonic nonce for sequencing | Incremented each proposal |
| `lastSignerChange` | `uint256` | Timestamp of last rotation | Set on `executeSignerChange` |
| `guardianApprovals` | `mapping(bytes32→mapping(address→bool))` | Per-guardian approval per callHash | Set by each guardian call |
| `guardianApprovalCount` | `mapping(bytes32→uint256)` | Total approvals per callHash | Incremented per guardian |

### Functions

**`createProposal(target, value, data) → proposalId`**
- Creates proposal with `executableAt = now + 24h`, `nonce = ++globalNonce`
- No auto-confirmation for proposer (unlike V1 — signer must separately call `signProposal`)

**`signProposal(proposalId)`**
- `onlySigner`, `nonReentrant`
- Validates existence, non-expiry, no double-sign
- Increments signature counter

**`executeProposal(proposalId)`**
- `nonReentrant`, open to any caller
- Validates all conditions, CEI pattern, forwards ETH value

**`proposeSignerChange(add, remove) → changeId`**
- `onlySigner`, subject to 7-day cooldown
- Starts approval at 1

**`approveSignerChange(changeId)`**
- `onlySigner`, validates non-expiry and no double-approval

**`executeSignerChange(changeId)`**
- `onlySigner`, `nonReentrant`
- Requires threshold approvals and 24h timelock
- Removes old signer via swap-and-pop, adds new

**`emergencyExecute(data, target)`**
- Open to guardians only
- Accumulates approvals; fires on third
- **Unpatched replay risk** — no `executedCallHashes` guard

**`getProposalStatus(proposalId) → Proposal`** — view, returns full struct

### Math

**Threshold enforcement at construction:**
```
require(_threshold * 10000 / _signers.length >= 5000)
```
This is integer arithmetic. For `_signers.length = 2`, `_threshold = 1`: `1 * 10000 / 2 = 5000 ≥ 5000` — passes. For `_signers.length = 3`, `_threshold = 1`: `1 * 10000 / 3 = 3333 < 5000` — fails.

**Emergency path:** requires `guardianApprovalCount[callHash] >= 3`.

### Events

| Event | Trigger | Key Fields |
|---|---|---|
| `ProposalCreated` | `createProposal` | proposalId, target, value |
| `ProposalSigned` | `signProposal` | proposalId, signer |
| `ProposalExecuted` | `executeProposal` | proposalId |
| `SignerAdded/Removed` | `executeSignerChange` | signer address |
| `ThresholdUpdated` | N/A in this version | — |
| `EmergencyApproved` | each guardian call | callHash, guardian, approvalCount |
| `EmergencyExecuted` | third guardian approval | callHash, target |
| `SignerChangeProposed/Approved/Executed` | signer change flow | changeId, add, remove |

### Security Invariants

1. Proposals require 24-hour timelock before execution
2. Proposals expire after 7 days
3. Threshold must be ≥ 50% of signer count (enforced at construction only)
4. Signer changes require threshold approvals + timelock + cooldown
5. Only the 3 fixed guardians can use `emergencyExecute`
6. **UNPATCHED:** `emergencyExecute` lacks `executedCallHashes` replay guard (CRIT-01)
7. **UNPATCHED:** no post-execution threshold floor check on self-call proposals (MED-21)
8. Reentrancy on `signProposal` and `executeProposal` blocked by OZ `ReentrancyGuard`

### Integration

This variant is used in test environments. Production deployments should use the audit-contracts variant (`vf-audit-contracts/MultisigGovernanceV3.sol`) which contains the CRIT-01 and MED-21 fixes.

### Dependencies + Rebuild Recipe

- **Import:** `@openzeppelin/contracts/utils/ReentrancyGuard.sol`
- **Constructor:** `(address[] _signers, uint256 _threshold, address[3] _guardians)`
- **Minimum viable deploy:** 2 signers with threshold=1 fails (MIN_THRESHOLD_BPS), minimum is 2-of-2

---

## 3. MultisigGovernanceV3 — `vf-audit-contracts/MultisigGovernanceV3.sol`

**Category:** Multisig governance (V3 with CRIT-01 and MED-21 audit fixes applied)

### Overview

Identical architecture to the test-fixes variant above, but with two critical audit remediations applied:

**CRIT-01 fix (lines 241–246):** `executedCallHashes` mapping tracks which `callHash`es have already been executed via `emergencyExecute`. The check-and-set is performed **before** the `.call` (strict CEI), preventing indefinite re-execution by guardians after the count reaches 3.

**MED-21 fix (lines 134–139):** After any proposal that targets `address(this)` executes (a self-call), the contract re-checks `threshold * 10000 / signers.length >= MIN_THRESHOLD_BPS`. This prevents a colluding majority from using a self-call proposal to lower the threshold below 50%, circumventing the construction-time invariant.

### Additional State Variable vs test-fixes

```solidity
// Line 59
mapping(bytes32 => bool) public executedCallHashes; // [audit-v3.3] CRIT-01 fix
```

### Modified emergencyExecute (lines 224–251)

```
Line 241  require(!executedCallHashes[callHash], "already executed");
Line 246  executedCallHashes[callHash] = true;  // EFFECT before INTERACTION
Line 248  (bool ok,) = target.call(data);
```

### Modified executeProposal (lines 118–142)

```
Line 134  if (p.target == address(this)) {
Line 136      require(threshold * 10000 / signers.length >= MIN_THRESHOLD_BPS, "threshold below minimum");
Line 139  }
```

All other behaviors, state variables, functions, math, and events are identical to the test-fixes variant documented above.

### Security Invariants (additions)

9. `executedCallHashes` prevents `emergencyExecute` replay (CRIT-01)
10. Self-call proposals cannot lower threshold below 50% (MED-21)

### This is the Canonical Production Variant

The `vf-audit-contracts/` directory contains the versions submitted for professional audit. This is the authoritative production-ready `MultisigGovernanceV3`.

---

## 4. MultisigGovernanceV3 — `v3-pr-ready/contracts/v3/MultisigGovernanceV3.sol`

**Category:** Multisig governance (V3 redesigned — "clean room" PR-ready variant)

### Overview

This is a substantially different architecture from the timelock variants above. It uses a **classic Gnosis Safe-style** confirmation model: no mandatory timelock, confirmations accumulate from signers, threshold triggers immediate execution readiness. Key additions from a 2026 audit:

- `MINIMUM_THRESHOLD = 2` — eliminates 1-of-N setups
- `MAX_SIGNERS = 20` — caps signer set size
- `pendingTxIds` array with O(1) removal — enables ghost-confirmation purge when a signer is removed
- `_pendingIndex` map for O(1) lookup into `pendingTxIds`
- `ETHReceived` event in `receive()`
- `OnlySelf` custom error replacing the `require` string in `onlySelf`
- `notExpired` added to `revokeConfirmation`
- `proposedAt` added to `getTransaction` return tuple

### Full Source Summary

```
Line  1–22   License, pragma, NatSpec (design notes, audit fix references)
Line 23       contract MultisigGovernanceV3 {
Line 25–44   Errors — 13 including MaxSignersReached, OnlySelf
Line 46–77   Events — includes ETHReceived
Line 79–89   struct Transaction { target, value, data, executed, confirmations, proposedAt, expiresAt }
Line 91–100  Constants: TRANSACTION_EXPIRY=7days, MINIMUM_THRESHOLD=2, MAX_SIGNERS=20
Line 102–128 State: signers[], isSigner, threshold, transactions, confirmations,
              transactionCount, pendingTxIds[], _pendingIndex
Line 130–156 Modifiers: onlySigner, onlySelf, txExists, notExecuted, notExpired
Line 158–187 Constructor: enforces MINIMUM_THRESHOLD, MAX_SIGNERS, dup-check
Line 189–280 Transaction lifecycle: propose (adds to pendingTxIds, auto-confirm),
              confirm, revoke (now notExpired), execute (removes from pendingTxIds)
Line 282–346 Signer management: addSigner (MAX_SIGNERS cap), removeSigner (ghost-purge sweep)
Line 348–361 changeThreshold: enforces MINIMUM_THRESHOLD
Line 363–423 View functions: getSigners, getSignerCount, getTransaction (+proposedAt),
              isTransactionReady, getPendingTxIds
Line 425–464 Internal: _confirm, _addToPending, _removeFromPending, _removeFromPendingAt
Line 466–471 receive() with ETHReceived event
```

### Critical Architecture: Ghost-Confirmation Purge

When `removeSigner(signer)` is called, the contract iterates the entire `pendingTxIds` array in reverse:

```solidity
// Lines 323–343
while (i > 0) {
    --i;
    uint256 txId = pendingTxIds[i];
    Transaction storage txn = transactions[txId];
    // Opportunistically clean expired entries
    if (block.timestamp > txn.expiresAt) {
        _removeFromPendingAt(i);
        continue;
    }
    // Revoke ghost confirmation
    if (confirmations[txId][signer]) {
        confirmations[txId][signer] = false;
        txn.confirmations--;
    }
}
```

This is an O(P) operation where P = number of pending transactions. In the worst case with many pending transactions, `removeSigner` could be expensive. The 20-signer cap and 7-day expiry limit practical P.

### pendingTxIds O(1) Management

```
_addToPending(txId):
    _pendingIndex[txId] = pendingTxIds.length
    pendingTxIds.push(txId)

_removeFromPendingAt(idx):
    movedTxId = pendingTxIds[last]
    pendingTxIds[idx] = movedTxId
    _pendingIndex[movedTxId] = idx
    pendingTxIds.pop()
```

Standard swap-and-pop with index maintenance. Removal is O(1).

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `signers` | `address[]` | Enumerable signer list | Via `addSigner`/`removeSigner` (self-only) |
| `isSigner` | `mapping(address→bool)` | O(1) membership | Signer management |
| `threshold` | `uint256` | Min confirmations, ≥ 2 | `changeThreshold` (self-only) |
| `transactions` | `mapping(uint256→Transaction)` | Proposal records | Written on propose, flag on execute |
| `confirmations` | `mapping(uint256→mapping(address→bool))` | Vote tracking | Confirm/revoke/ghost-purge |
| `transactionCount` | `uint256` | Next txId (post-increment) | Increments on propose |
| `pendingTxIds` | `uint256[]` | Active (non-executed) proposal IDs | Add on propose, remove on execute/expire |
| `_pendingIndex` | `mapping(uint256→uint256)` | txId → position in pendingTxIds | Managed by _addToPending/removePending |

### Math

**Minimum threshold:** `threshold >= MINIMUM_THRESHOLD = 2` at all times.

**Threshold bounds:** `2 ≤ threshold ≤ signers.length ≤ 20`

**Ghost purge cost:** O(pendingTxIds.length) per `removeSigner`

### Events (additional vs V1)

| Event | Trigger | Purpose |
|---|---|---|
| `ETHReceived` | `receive()` | Audit trail for incoming ETH |

### Security Invariants

1. `threshold ≥ 2` enforced by constructor and `changeThreshold`
2. `signers.length ≤ 20` enforced by `addSigner`
3. Ghost confirmations purged on `removeSigner`
4. Expired pending transactions opportunistically cleaned during `removeSigner`
5. `revokeConfirmation` now also checks `notExpired`
6. No guardian / emergency path in this variant (simpler design)
7. `onlySelf` uses custom error `OnlySelf` not a require string

---

## 5. `push_contracts_v3_MultisigGovernanceV3.sol`

**Category:** Multisig governance (push branch — identical to early V3 timelock, pre-CRIT-01 fix)

### Overview

This is the version pushed to an early `push_contracts_v3` branch. It is identical to `test_fixes/contracts/MultisigGovernanceV3.sol` in structure and behavior. The `emergencyExecute` path lacks `executedCallHashes` (CRIT-01 still present). The `proposeSignerChange` collapses the add/remove into a single atomic function call (no separate approval flow) — the proposer atomically executes the swap:

```solidity
// Lines 128–151: proposeSignerChange is a direct atomic swap
function proposeSignerChange(address add, address remove) external onlySigner {
    // ...cooldown, dup checks...
    isSigner[remove] = false;  // remove immediately
    // swap-and-pop signers[]
    isSigner[add] = true;      // add immediately
    signers.push(add);
    lastSignerChange = block.timestamp;
}
```

This means a single signer can rotate the set without threshold approval — a vulnerability that the `test_fixes` and `vf-audit-contracts` variants address by converting `proposeSignerChange` into a multi-step approval flow (`proposeSignerChange` → `approveSignerChange` × N → `executeSignerChange`).

**Differences from patched variants:**
- No `SignerChangeRequiresThreshold` error
- No `SignerChangeRequest` struct
- No `signerChanges` / `signerChangeApprovals` / `signerChangeCount` state
- `proposeSignerChange` has no return value and executes atomically
- `emergencyExecute` has no `executedCallHashes` replay guard

**All other behaviors** (constants, Proposal struct, `createProposal`, `signProposal`, `executeProposal`, `getProposalStatus`) are identical to the test-fixes variant.

### Security Invariants — Gaps

- **HIGH-MULTISIG-01:** Any single signer can unilaterally rotate the signer set (patched in test_fixes)
- **CRIT-01:** `emergencyExecute` replay (patched in vf-audit-contracts)

---

## 6. AntiSurveillance — `contracts/AntiSurveillance.sol`

**Category:** Anti-surveillance enforcement

### Full Source Summary

```
  1  // SPDX-License-Identifier: MIT
  2  pragma solidity ^0.8.25;
  4–17  NatSpec: privacy-preserving by design, no freeform strings, non-binding reports,
         owner/governance controlled enforcement
18  contract AntiSurveillance {
19–26  address public owner; events OwnershipTransferred; modifier onlyOwner
33–36  string public constant ANTI_SURVEILLANCE_COMMITMENT
42–52  enum BannedDataType { BEHAVIORAL_TRACKING, CROSS_PROTOCOL_LINKING,
        METADATA_HARVESTING, BIOMETRIC_DATA, LOCATION_DATA, DEVICE_FINGERPRINTING,
        SOCIAL_GRAPH_MINING, SENTIMENT_ANALYSIS, PREDICTIVE_PROFILING }
57–64  mapping(address => uint256) public surveillanceFreeModules
        mapping(address => bool) public bannedForSurveillance
66–81  Events: ModuleVerifiedSurveillanceFree, ModuleBannedForSurveillance
        (uses hashes not strings), SurveillanceAttemptBlocked
83–87  modifier surveillanceFreeOnly(address module)
89–91  constructor: owner = msg.sender
93–98  transferOwnership
101–113 reportSurveillanceViolation: permissionless, non-binding, emits with zero reasonHash
116–125 banModuleForSurveillance: onlyOwner, binding, sets bannedForSurveillance[module]=true
128–130 unbanModule: onlyOwner
132–142 verifyModuleSurveillanceFree: onlyOwner, sets timestamp
144–163 getAntiSurveillancePolicy, isDataTypeBanned, getBannedDataTypes (pure views)
```

### Line-by-Line Walkthrough

**Lines 33–36 — ANTI_SURVEILLANCE_COMMITMENT constant.** Immutable string encoded in bytecode. Cannot be changed post-deployment. Acts as an on-chain policy declaration.

**Lines 42–52 — BannedDataType enum.** Defines nine categories of prohibited data collection. Every value maps to a `uint8` (0–8). Used in events to log violation type without emitting freeform text.

**Lines 57–64 — State.** `surveillanceFreeModules` stores the `block.timestamp` of verification (non-zero = verified). `bannedForSurveillance` is a simple boolean flag. Once banned, `verifyModuleSurveillanceFree` cannot be called on that module (`require(!bannedForSurveillance[module])`).

**Lines 66–81 — Events.** `ModuleBannedForSurveillance` accepts `reasonHash` and `evidenceHash` instead of strings — a deliberate privacy choice. Off-chain systems publish the signed report; on-chain stores only the hash. `SurveillanceAttemptBlocked` is defined but never emitted in this contract — intended for composing modules that inherit `surveillanceFreeOnly`.

**Lines 83–87 — surveillanceFreeOnly modifier.** Dual check: module must be verified AND not subsequently banned. Intended for use by other contracts that want to gate operations on surveillance-free module status.

**Lines 101–113 — reportSurveillanceViolation.** Permissionless. Anyone can call. Non-binding — emits the event with `reasonHash = bytes32(0)` (unknown/unspecified). This is explicitly designed to be non-binding to prevent griefing/DoS attacks.

**Lines 116–125 — banModuleForSurveillance.** `onlyOwner`. Binding governance action. Takes both `reasonHash` and `evidenceHash` (both must be non-zero). Sets `bannedForSurveillance[module] = true` permanently until `unbanModule`.

**Lines 153–155 — isDataTypeBanned.** Returns `true` for ALL data types — the comment explicitly states `dataType; // suppress unused variable warning`. This is intentional: all 9 banned data types are always banned.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `owner` | `address` | Governance authority | Changed by `transferOwnership` |
| `surveillanceFreeModules` | `mapping(address→uint256)` | Verification timestamp (0 = not verified) | Set by `verifyModuleSurveillanceFree` |
| `bannedForSurveillance` | `mapping(address→bool)` | Module ban flag | Set/cleared by owner |

### Functions

| Function | Access | Effect |
|---|---|---|
| `transferOwnership(newOwner)` | `onlyOwner` | Atomically transfers ownership, no two-step |
| `reportSurveillanceViolation(module, type, evidenceHash)` | Public | Emits event only, non-binding |
| `banModuleForSurveillance(module, type, reasonHash, evidenceHash)` | `onlyOwner` | Sets ban flag, emits event |
| `unbanModule(module)` | `onlyOwner` | Clears ban flag |
| `verifyModuleSurveillanceFree(module)` | `onlyOwner` | Sets timestamp, cannot re-verify banned modules |
| `getAntiSurveillancePolicy()` | `pure` | Returns policy string |
| `isDataTypeBanned(dataType)` | `pure` | Always returns true |
| `getBannedDataTypes()` | `pure` | Returns human-readable list |

### Events

| Event | When Fired | Logged Data |
|---|---|---|
| `ModuleVerifiedSurveillanceFree` | `verifyModuleSurveillanceFree` | module, timestamp |
| `ModuleBannedForSurveillance` | `banModuleForSurveillance` + `reportSurveillanceViolation` | module, violationType, reasonHash, evidenceHash, timestamp |
| `SurveillanceAttemptBlocked` | Defined but never emitted in this contract | attacker, attemptedType, timestamp |
| `OwnershipTransferred` | `transferOwnership` | previous, new |

### Security Invariants

1. All 9 banned data types are permanently banned — `isDataTypeBanned` always returns true
2. Public reports are non-binding — only owner can effect module state changes
3. Evidece is stored as hashes only — no PII written on-chain
4. A banned module cannot be re-verified without unbanning first
5. `transferOwnership` is single-step — use multisig address as owner in production
6. The `surveillanceFreeOnly` modifier is available for composing contracts to gate operations

### Integration

`AntiSurveillance` is used as a registry by protocol modules that want to assert surveillance-free status. The `surveillanceFreeOnly` modifier can be imported/used by bond contracts and the identity registry to gate sensitive operations.

---

## 7. PrivacyGuarantees — `contracts/PrivacyGuarantees.sol`

**Category:** Privacy guarantees (consent and deletion management)

### Full Source Summary

```
  1  // SPDX-License-Identifier: MIT
  2  pragma solidity ^0.8.25;
  4–16  NatSpec: privacy floor for modules, hashed purposes, no freeform strings
17  contract PrivacyGuarantees {
22  string public constant PRIVACY_POLICY_VERSION = "Vaultfire-v1.1-NoKYC-WalletOnly-HashedPurposes"
30  uint256 public constant DATA_RETENTION_PERIOD = 30 days
33  mapping(address => uint256) public deletionRequests
36  mapping(address => mapping(bytes32 => bool)) public userConsent
38–42  Events: DataDeletionRequested, DataPermanentlyDeleted, ConsentGranted, ConsentRevoked
44–52  Modifiers: requiresConsent, checkDeletionRequest
58–60  hashPurpose(purpose) → bytes32: pure helper
66–80  grantConsent(purposeHash), revokeConsent(purposeHash)
87–98  requestDataDeletion(), cancelDeletionRequest()
100–107 hasConsent(), isDeletionEnforceable()
109–115 getPrivacyPolicy() pure
```

### Line-by-Line Walkthrough

**Line 22 — PRIVACY_POLICY_VERSION.** An immutable identifier anchoring this deployment to a specific policy version. If the privacy policy materially changes, a new contract is deployed with a new version string.

**Line 30 — DATA_RETENTION_PERIOD = 30 days.** The period between a user's deletion request and when off-chain systems are required to have completed deletion/redaction. On-chain state is never truly deleted (blockchain immutability), but this signals the 30-day off-chain obligation window.

**Line 33 — deletionRequests.** Maps user address to the `effectiveAt` timestamp (`block.timestamp + 30 days`) of their deletion request, or 0 if no request is pending.

**Line 36 — userConsent.** Double mapping: `user → purposeHash → bool`. Purposes are represented as `keccak256` hashes of human-readable strings. The human-readable strings never touch the chain.

**Lines 44–52 — Modifiers.** `requiresConsent(user, purposeHash)` is designed for use by inheriting contracts to gate data operations. `checkDeletionRequest(user)` prevents new data writes for users who have requested deletion.

**Lines 66–80 — grantConsent / revokeConsent.** Both are `msg.sender`-based — users can only grant/revoke their own consent. `purposeHash != bytes32(0)` prevents vacuous consents.

**Lines 87–93 — requestDataDeletion.** One deletion request per user at a time (idempotency guard). Sets `effectiveAt = now + 30 days`. Emits `DataDeletionRequested` with the effective timestamp.

**Lines 95–98 — cancelDeletionRequest.** Allows user to rescind a pending deletion request before it becomes effective.

**Lines 104–107 — isDeletionEnforceable.** Returns `(true, effectiveAt)` only when `effectiveAt > 0 AND now >= effectiveAt`. Off-chain systems should poll this to know when to execute deletion.

**Note:** `DataPermanentlyDeleted` event is defined but **never emitted** in this contract. It is intended to be emitted by off-chain systems or composing contracts that actively confirm deletion completion.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `deletionRequests` | `mapping(address→uint256)` | effectiveAt timestamp (0 = none) | Set by `requestDataDeletion`, cleared by `cancelDeletionRequest` |
| `userConsent` | `mapping(address→mapping(bytes32→bool))` | Consent flags per purpose | Set/cleared by `grantConsent`/`revokeConsent` |

### Functions

| Function | Access | Effect |
|---|---|---|
| `hashPurpose(purpose)` | `public pure` | `keccak256(abi.encodePacked(purpose))` — helper for UI integration |
| `grantConsent(purposeHash)` | `external` | Sets `userConsent[msg.sender][hash] = true` |
| `revokeConsent(purposeHash)` | `external` | Sets to `false`, emits `ConsentRevoked` |
| `requestDataDeletion()` | `external` | Sets `deletionRequests[msg.sender] = now + 30 days` |
| `cancelDeletionRequest()` | `external` | Clears deletion request |
| `hasConsent(user, purposeHash)` | `public view` | Returns consent flag |
| `isDeletionEnforceable(user)` | `public view` | Returns `(enforceable, effectiveAt)` |
| `getPrivacyPolicy()` | `public pure` | Returns human-readable policy string |

### Events

| Event | When Fired | Data |
|---|---|---|
| `DataDeletionRequested` | `requestDataDeletion` | user, effectiveAt |
| `DataPermanentlyDeleted` | **Never emitted on-chain** — for off-chain/composing contracts | user, timestamp |
| `ConsentGranted` | `grantConsent` | user, purposeHash |
| `ConsentRevoked` | `revokeConsent` | user, purposeHash |

### Security Invariants

1. Users can only manage their own consent — no third-party delegation
2. Deletion requests are idempotent — one pending request per address
3. Purpose hashes prevent PII in consent data
4. `DATA_RETENTION_PERIOD` is immutable — cannot be shortened by governance
5. `PRIVACY_POLICY_VERSION` is immutable — policy changes require new deployment
6. The `DataPermanentlyDeleted` event being off-chain means on-chain state is not erased (by design — blockchain immutability)

### Integration

`PrivacyGuarantees` is a composable base contract. Other modules can `import` it and use `requiresConsent` and `checkDeletionRequest` modifiers to gate data writes. The ERC-8004 identity and reputation registries are expected to check `isDeletionEnforceable` before processing updates for users who have requested deletion.

---

## 8. MissionEnforcement — `contracts/MissionEnforcement.sol`

**Category:** Mission enforcement (protocol-level ethics registry)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
 25  contract MissionEnforcement {
 30–33  address public owner; address public pendingOwner; events; onlyOwner modifier
 47–50  string public constant MISSION_STATEMENT
 56–65  enum CorePrinciple { HUMAN_VERIFICATION_FINAL_SAY, AI_PROFIT_CAPS, PRIVACY_DEFAULT,
         COMMUNITY_CHALLENGES, OPEN_SOURCE_VERIFIABLE, NO_KYC_WALLET_ONLY, NO_DATA_SALE,
         RIGHT_TO_BE_FORGOTTEN }
 71  mapping(address => mapping(CorePrinciple => bool)) public missionCompliance
 76–83  struct MissionReport { reporter, timestamp, principle, evidence }
 83  mapping(address => MissionReport[]) public missionReports
 89  mapping(address => uint256) public missionCompliantModules
 95  mapping(address => CorePrinciple) public missionViolations
100–130 Events: ModuleMissionCompliant, MissionViolationReported, MissionViolationDetected,
         MissionBypassAttemptBlocked
135–138 modifier enforceMission(address module, CorePrinciple principle)
147–157 verifyAIProfitCap(aiShare, totalAmount, capPercentage) → bool (pure math)
167–178 verifyHumanFinalSay(aiMetricScore, humanVerificationScore, hasHumanVerification) → uint256
185–192 verifyNoKYC(dataType) → bool — only allows "wallet_address"
203–219 reportMissionViolation: permissionless, non-binding, stores MissionReport
226–238 adjudicateMissionViolation: onlyOwner, binding, sets violation + compliance=false
246–258 certifyModuleMissionCompliant: onlyOwner, marks per-principle + timestamps module
261–263 constructor: owner = msg.sender
268–284 transferOwnership (two-step) / acceptOwnership
290–322 getMissionStatement, getMissionEnforcementPolicy, isCompliantWithPrinciple,
         getAllCorePrinciples (returns 0xFF = all 8 bits set)
```

### Line-by-Line Walkthrough

**Lines 47–50 — MISSION_STATEMENT.** Immutable string: "For happy and healthy humans, AIs, and Earth. AI grows WITH humans, not ABOVE them. Privacy over surveillance. Freedom over control. Morals over metrics." Cannot be changed.

**Lines 56–65 — CorePrinciple enum.** Eight values mapping to uint8 0–7. Used as keys in `missionCompliance[module][principle]`.

**Line 71 — missionCompliance.** Module address → principle → compliance flag. Initially all false. Owner calls `certifyModuleMissionCompliant` to set flags true. `adjudicateMissionViolation` sets them false.

**Line 89 — missionCompliantModules.** Stores the timestamp when a module was last certified. Acts as an index for "has this module been certified at all."

**Line 95 — missionViolations.** Stores the most recent violation principle for a module. Only one principle is stored per module address.

**Lines 135–138 — enforceMission modifier.** Reverts if `missionCompliance[module][principle]` is false. Designed for use by bonding contracts that check compliance before allowing AI profit distributions.

**Lines 147–157 — verifyAIProfitCap.** Pure math function:
```solidity
uint256 maxAIShare = (totalAmount * capPercentage) / 100;
return aiShare <= maxAIShare;
```
Used by `AIAccountabilityBondsV2` (30% cap) and `AIPartnershipBondsV2` (50% cap) before distributing yields.

**Lines 167–178 — verifyHumanFinalSay.** If `hasHumanVerification = true`, returns `humanVerificationScore` unconditionally, overriding the AI metric. This is the on-chain expression of "human verification always has final say."

**Lines 185–192 — verifyNoKYC.** Accepts a string `dataType`, hashes it, and checks equality against `keccak256("wallet_address")`. Returns true only if data type is exactly "wallet_address". All other data types — names, emails, government IDs, etc. — will hash differently and return false.

**Lines 203–219 — reportMissionViolation.** Permissionless. Stores a `MissionReport` in `missionReports[module]` (unbounded array). Evidence is a string (up to 1000 bytes). Note: this **does** write PII-risk freeform evidence strings on-chain — a design trade-off compared to `AntiSurveillance` which uses hashes. Non-binding until adjudicated.

**Lines 226–238 — adjudicateMissionViolation.** Owner action. Sets `missionViolations[module] = principle` (overwriting any previous violation) and sets `missionCompliance[module][principle] = false`. Emits `MissionViolationDetected`.

**Lines 246–258 — certifyModuleMissionCompliant.** Iterates the `principles` array, sets each compliance flag to true, then timestamps the module. Emits `ModuleMissionCompliant`.

**Lines 268–284 — Two-step ownership transfer.** Uses `pendingOwner` pattern. `transferOwnership` sets `pendingOwner` and emits `OwnershipTransferStarted`. `acceptOwnership` (callable only by `pendingOwner`) completes the transfer.

**Line 319 — getAllCorePrinciples.** Returns `0xFF` — all 8 bits set, representing all 8 principles in bitmask form.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `owner` | `address` | Governance authority | Two-step transfer |
| `pendingOwner` | `address` | Pending new owner | Set by `transferOwnership`, cleared on accept |
| `missionCompliance` | `mapping(address→mapping(CorePrinciple→bool))` | Per-module per-principle compliance | Owner-controlled |
| `missionReports` | `mapping(address→MissionReport[])` | Community violation reports | Append-only by anyone |
| `missionCompliantModules` | `mapping(address→uint256)` | Certification timestamps | Owner-controlled |
| `missionViolations` | `mapping(address→CorePrinciple)` | Most recent violation per module | Owner-controlled |

### Math — AI Profit Cap Verification

```
maxAIShare = totalAmount × capPercentage / 100
```
- Partnership bonds: `capPercentage = 30` → max 30% to AI
- Accountability bonds: `capPercentage = 50` → max 50% to AI
- Integer division: rounds down, so `aiShare ≤ floor(total × cap / 100)`

### Events

| Event | When | Key Data |
|---|---|---|
| `ModuleMissionCompliant` | `certifyModuleMissionCompliant` | module, timestamp |
| `MissionViolationReported` | `reportMissionViolation` | module, reporter, principle, evidence |
| `MissionViolationDetected` | `adjudicateMissionViolation` | module, principle, evidence |
| `MissionBypassAttemptBlocked` | Defined, never emitted here — for composing contracts | module, principle |
| `OwnershipTransferStarted` | `transferOwnership` | previous, new |
| `OwnershipTransferred` | `acceptOwnership` | previous, new |

### Security Invariants

1. `MISSION_STATEMENT` is immutable
2. `CorePrinciple` enum is immutable
3. Certification requires explicit owner action per principle per module
4. Reports are non-binding — no state change from `reportMissionViolation`
5. Two-step ownership prevents accidental mis-transfers
6. `verifyAIProfitCap` is pure — no state writes, can be called by any contract
7. Evidence strings in `reportMissionViolation` can be up to 1000 bytes — potential gas cost
8. `missionReports` array is unbounded — could theoretically grow very large for targeted modules

### Integration

`MissionEnforcement` is referenced by `AIAccountabilityBondsV2` and `AIPartnershipBondsV2`. The bond contracts call `missionContract.verifyAIProfitCap()` before distribution. The `enforceMission` modifier can gate critical operations. This contract is the on-chain expression of Vaultfire's "Morals over Metrics" principle.

---

## 9. VaultfireTaskEscrow — `VaultfireTaskEscrow.sol`

**Category:** Task escrow

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
 23  interface IERC20 { transferFrom, transfer, balanceOf }
 31  interface IERC8004IdentityRegistry { isAgentActive(address) → bool }
40  contract VaultfireTaskEscrow {
46–55  enum TaskStatus { Created, Bidding, Assigned, WorkSubmitted, Completed, Disputed, Cancelled }
57–68  struct Task { id, poster, description, reward, deadline, status, assignee, deliverableURI, createdAt }
70–75  struct Bid { bidder, proposal, timestamp }
82  uint256 public constant FEE_BPS = 100; // 1%
85  uint256 private constant BPS_DENOM = 10_000;
92  IERC8004IdentityRegistry public immutable identityRegistry;
95  address public immutable governance;
98  IERC20 public immutable usdc;
101 address public immutable feeRecipient;
108 uint256 private _taskCounter;
111 mapping(uint256 => Task) private _tasks;
114 mapping(uint256 => Bid[]) private _bids;
120–129 Manual ReentrancyGuard (NOT_ENTERED=1, ENTERED=2)
135–194 Events: TaskCreated, BidPlaced, BidAccepted, WorkSubmitted, WorkApproved,
          TaskDisputed, DisputeResolved, TaskCancelled
207–222 constructor: validates all 4 immutables non-zero
228–235 modifier onlyRegisteredAgent (calls identityRegistry.isAgentActive)
238–241 modifier taskExists (bounds check on _taskCounter)
256–289 createTask: pulls USDC, mints task record
298–328 bidOnTask: agent-only, not poster, not expired deadline
336–354 acceptBid: poster-only, Bidding status, valid bid index
362–378 submitWork: assignee-only, Assigned status, before deadline
386–410 approveWork: poster-only, WorkSubmitted status, splits fee
419–439 disputeTask: poster or assignee, Assigned or WorkSubmitted, sets Disputed
449–472 resolveDispute: governance-only, Disputed status, full reward to winner
481–502 cancelTask: poster-only, Created/Bidding status, full refund
513–542 View: getTask, getTaskBids, taskCount
```

### Line-by-Line Walkthrough

**Lines 46–55 — TaskStatus enum.** Seven states. `Created` → `Bidding` (on first bid) → `Assigned` → `WorkSubmitted` → `Completed`. Dispute branch: `Assigned` or `WorkSubmitted` → `Disputed` → `Completed`. Cancellation: `Created` or `Bidding` → `Cancelled`.

**Lines 82–85 — FEE_BPS.** Protocol fee is exactly 100 basis points = 1%. BPS denominator is 10,000.

**Lines 92–101 — Immutables.** All four addresses are set at construction and permanently immutable: `identityRegistry` for agent verification, `governance` for dispute resolution (must be `MultisigGovernance` address), `usdc` for escrow token, `feeRecipient` for fee collection.

**Lines 271–273 — createTask USDC pull.** Uses `transferFrom` — caller must have pre-approved the escrow contract for `reward` USDC. The contract does NOT use `safeTransferFrom`; it checks the return value with `require(ok, ...)`.

**Lines 398–407 — approveWork fee split:**
```solidity
uint256 fee          = (task.reward * FEE_BPS) / BPS_DENOM;  // 1%
uint256 workerAmount = task.reward - fee;                      // 99%
usdc.transfer(task.assignee, workerAmount);  // to worker
usdc.transfer(feeRecipient, fee);             // to protocol
```

**Lines 449–472 — resolveDispute.** Only `governance` address can call. Directs 100% of the escrowed USDC to either poster or assignee — no fee deducted on disputed resolution. `payTo` must be one of the two parties.

**Lines 481–502 — cancelTask.** Only before a bid is accepted. Full reward refunded — no fee on cancellations.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `identityRegistry` | `IERC8004IdentityRegistry` | Agent verification | `immutable` |
| `governance` | `address` | Dispute resolver | `immutable` |
| `usdc` | `IERC20` | Escrow token | `immutable` |
| `feeRecipient` | `address` | Protocol fee destination | `immutable` |
| `_taskCounter` | `uint256` | Task ID counter (1-based) | Incremented on `createTask` |
| `_tasks` | `mapping(uint256→Task)` | Task records | Written on create, status updated through lifecycle |
| `_bids` | `mapping(uint256→Bid[])` | Bids per task | Appended on `bidOnTask` |

### Math — Escrow Release

```
fee          = reward × 100 / 10000 = reward × 1%
workerAmount = reward − fee         = reward × 99%
```

**On dispute resolution:** full `reward` (100%) goes to `payTo`. No fee deducted.
**On cancellation:** full `reward` (100%) refunded to poster. No fee deducted.

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `TaskCreated` | `createTask` | taskId, poster, reward, deadline, description |
| `BidPlaced` | `bidOnTask` | taskId, bidder, bidIndex, proposal |
| `BidAccepted` | `acceptBid` | taskId, assignee, bidIndex |
| `WorkSubmitted` | `submitWork` | taskId, assignee, deliverableURI |
| `WorkApproved` | `approveWork` | taskId, assignee, workerAmount, feeAmount |
| `TaskDisputed` | `disputeTask` | taskId, disputedBy, reason |
| `DisputeResolved` | `resolveDispute` | taskId, governance, payTo, amount |
| `TaskCancelled` | `cancelTask` | taskId, poster, refundAmount |

### Security Invariants

1. Only registered agents can create tasks, bid, or submit work
2. Poster cannot bid on their own task
3. Work must be submitted before deadline
4. Dispute resolution is exclusively the governance multisig
5. Dispute payout is full reward — no fee on contested settlement
6. CEI respected: status updated before ETH/token transfers
7. Reentrancy blocked by manual guard
8. `governance` is immutable — cannot be changed post-deployment

### Integration

`VaultfireTaskEscrow` requires three other Vaultfire contracts at deployment: `ERC8004IdentityRegistry` (for `isAgentActive`), `MultisigGovernance` (as dispute resolver), and a USDC ERC-20. The `VaultfireDisputeResolution` contract is a more sophisticated panel-based alternative to the governance-only resolution here.

---

## 10. VaultfireDisputeResolution — `theloopbreaker-site/contracts/VaultfireDisputeResolution.sol`

**Category:** Dispute resolution (panel-based arbitration)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
 26  interface IIdentityRegistry_DR { isAgentActive(address) → bool }
34  contract VaultfireDisputeResolution {
40–46  enum DisputeStatus { Filed, PanelSet, Voting, Resolved, Cancelled }
48–52  enum Vote { None, ForClaimant, ForRespondent }
54–67  struct Dispute { id, claimant, respondent, stakeAmount, description, evidenceHash,
         status, createdAt, resolvedAt, winner, votesForClaimant, votesForRespondent }
69–74  struct ArbitratorRecord { totalCasesAssigned, totalCasesVoted, totalFeesEarned, active }
81  uint256 public constant MIN_DISPUTE_STAKE = 0.001 ether
84  uint256 public constant ARBITRATOR_FEE_BPS = 500; // 5%
89  uint256 public constant MIN_PANEL_SIZE = 3
92  uint256 public constant MAX_PANEL_SIZE = 7
98  IIdentityRegistry_DR public immutable identityRegistry
99  address public immutable owner
105 uint256 private _disputeCounter
106 mapping(uint256 => Dispute) private _disputes
109 mapping(uint256 => address[]) private _panels
112 mapping(uint256 => mapping(address => Vote)) private _votes
115 mapping(address => ArbitratorRecord) public arbitrators
118–120 Global stats: totalDisputesFiled, totalDisputesResolved, totalStakeDisputed
122–135 Manual ReentrancyGuard
140–174 Events: DisputeFiled, PanelAssigned, VoteCast, DisputeResolved, DisputeCancelled
180–184 constructor
190–201 Modifiers: onlyOwner, onlyRegisteredAgent
214–252 fileDispute: payable, requires MIN_STAKE, stores dispute, updates stats
261–304 assignPanel: onlyOwner, validates size (odd, 3–7), no party on own panel, no duplicates
311–367 castVote: verifies panel membership, auto-resolves when all votes cast
373–408 _resolveDispute: internal, determines winner, splits fee, pays winner + arbitrators
414–433 cancelDispute: claimant-only, Filed status only, refunds stake
439–483 View functions: getDispute, getPanel, getVote, getArbitratorRecord, getDisputeStats, disputeCount
```

### Line-by-Line Walkthrough

**Lines 80–92 — Constants.** Minimum stake is 0.001 ETH. Arbitrator fee is 5% of the stake (`ARBITRATOR_FEE_BPS = 500`). Panel must be odd-sized (no ties), minimum 3, maximum 7.

**Lines 214–252 — fileDispute.** `payable`, `nonReentrant`, `onlyRegisteredAgent`. Claimant provides ETH stake. Evidence is stored as a `keccak256` hash (evidenceHash) — the actual evidence URI lives off-chain. Increments `totalDisputesFiled` and `totalStakeDisputed`.

**Lines 261–304 — assignPanel.** `onlyOwner`. Validates panel size is odd (prevents ties). Checks each member is a registered agent. Checks no member is the claimant or respondent. Checks for duplicates via O(n²) loop — acceptable because panel max is 7 (max 21 comparisons). Sets dispute to `PanelSet`.

**Lines 311–367 — castVote.** Open to any caller; verifies `msg.sender` is on the panel via linear scan (max 7 iterations). `Vote.None = 0` means unvoted; `Vote.ForClaimant = 1` or `Vote.ForRespondent = 2`. Once all panel members have voted, `_resolveDispute` is called automatically.

**Lines 373–408 — _resolveDispute (internal):**

```solidity
uint256 totalFee = (d.stakeAmount * ARBITRATOR_FEE_BPS) / BPS_DENOM; // 5%
uint256 feePerArbitrator = totalFee / panel.length;                    // even split
uint256 payout = d.stakeAmount - totalFee;                             // 95% to winner
```

Winner is determined by simple majority. The fee is integer-divided among panel members — remainders are effectively burned (stay in the contract). Payments via `.call{value}("")` with individual require checks per arbitrator.

**Lines 414–433 — cancelDispute.** Only claimant can cancel. Only allowed in `Filed` status (before panel assignment). Full stake refunded.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `_disputeCounter` | `uint256` | Monotonic dispute ID counter | Incremented on `fileDispute` |
| `_disputes` | `mapping(uint256→Dispute)` | All dispute records | Status transitions through lifecycle |
| `_panels` | `mapping(uint256→address[])` | Arbitration panels | Set on `assignPanel` |
| `_votes` | `mapping(uint256→mapping(address→Vote))` | Per-arbitrator vote records | Set on `castVote` |
| `arbitrators` | `mapping(address→ArbitratorRecord)` | Arbitrator performance tracking | Updated on assignment and voting |
| `totalDisputesFiled` | `uint256` | Global stat | Incremented on `fileDispute` |
| `totalDisputesResolved` | `uint256` | Global stat | Incremented on `_resolveDispute` |
| `totalStakeDisputed` | `uint256` | Total ETH put at stake | Incremented on `fileDispute` |

### Math — Payout Splits

```
total stake = S
arbitrator pool = S × 500 / 10000 = S × 5%
winner payout   = S − arbitratorPool
fee per member  = arbitratorPool / panel.length  (integer division, remainder lost)
```

For a 3-member panel and S = 1 ETH:
- `arbitratorPool = 0.05 ETH`
- `winner payout  = 0.95 ETH`
- `feePerArbitrator = 0.05 / 3 ≈ 0.0166 ETH` (0.0002 ETH remainder stays in contract)

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `DisputeFiled` | `fileDispute` | disputeId, claimant, respondent, stakeAmount, description |
| `PanelAssigned` | `assignPanel` | disputeId, panel[], timestamp |
| `VoteCast` | `castVote` | disputeId, arbitrator, vote, timestamp |
| `DisputeResolved` | `_resolveDispute` | disputeId, winner, payout, feePerArbitrator, timestamp |
| `DisputeCancelled` | `cancelDispute` | disputeId, timestamp |

### Security Invariants

1. Panel size is always odd (no ties)
2. Panel members cannot be dispute parties
3. No duplicate panel members
4. All panel members must be registered agents
5. Double-voting prevented by `Vote.None` initial state check
6. Auto-resolution fires only when all votes are in
7. Cancellation only allowed before panel assignment
8. Reentrancy blocked by manual guard on all state-changing functions
9. **Fee remainder stays in contract** — no withdrawal mechanism for leftover ETH (potential dust accumulation)

### Integration

`VaultfireDisputeResolution` is the sophisticated alternative to `VaultfireTaskEscrow.resolveDispute`. It is an independent contract; `VaultfireTaskEscrow` in its current form uses `governance` (MultisigGovernance address) rather than this contract, but the design intent is for high-value disputes to escalate to this panel system.

---

## 11. VaultfireCapabilityCredentials — `theloopbreaker-site/contracts/VaultfireCapabilityCredentials.sol`

**Category:** Capability credentials

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
 22  interface IIdentityRegistry_CC { isAgentActive(address) → bool }
30  contract VaultfireCapabilityCredentials {
36–45  struct Credential { id, agent, capabilityHash, issuedBy, issuedAt, expiresAt,
         taskProofCount, active }
47–51  struct AgentCredentialProfile { totalCredentials, activeCredentials, totalTaskProofs }
57  IIdentityRegistry_CC public immutable identityRegistry
58  address public immutable owner
64  uint256 private _credentialCounter
65  mapping(uint256 => Credential) private _credentials
66  mapping(address => AgentCredentialProfile) public agentProfiles
69  mapping(address => mapping(bytes32 => uint256)) public agentCapability
72  mapping(address => uint256[]) private _agentCredentials
75  mapping(address => bool) public approvedIssuers
78  mapping(bytes32 => address[]) private _capabilityHolders
84–93  Manual ReentrancyGuard
99–124 Events: CredentialIssued, CredentialRevoked, TaskProofAdded, IssuerApproved, IssuerRemoved
130  constructor: identityRegistry_ immutable, owner = deployer, owner is first issuer
142–153 Modifiers: onlyOwner, onlyIssuer
159–168 approveIssuer, removeIssuer (onlyOwner)
181–247 issueCredential: validates agent active, expiry, handles expired replacement
249–281 addTaskProof: onlyIssuer, increments proof count on active credential
283–309 revokeCredential: issuer or owner, clears agentCapability lookup
315–353 View: getCredential, getAgentCredentials, getCapabilityHolders, hasActiveCredential,
          getAgentProfile, credentialCount
```

### Line-by-Line Walkthrough

**Lines 36–45 — Credential struct.** The `capabilityHash = keccak256(capability_name_string)` encodes what capability is certified (e.g., `keccak256("data-analysis")`). `expiresAt = 0` means permanent. `taskProofCount` accumulates verified task completions.

**Line 69 — agentCapability.** Two-level map: `agent → capabilityHash → credentialId`. Zero means no credential. Enables O(1) lookup: "does this agent have an active credential for this capability?"

**Line 78 — _capabilityHolders.** Reverse map: `capabilityHash → address[]`. Allows task routing systems to find all agents with a given credential. Unbounded array — could grow with adoption.

**Lines 181–247 — issueCredential.** Checks the agent is active in the identity registry. Checks `expiresAt` is in the future (or zero). Checks for pre-existing credentials with the same `capabilityHash` — if one exists and is expired or inactive, it deactivates it before issuing new. If an active non-expired credential exists, reverts. Assigns `credentialId = ++_credentialCounter`. Updates both the forward (`_agentCredentials`) and reverse (`_capabilityHolders`) indexes.

**Lines 254–281 — addTaskProof.** `onlyIssuer`. Validates the credential is active and not expired. Increments `cred.taskProofCount` and `agentProfiles[cred.agent].totalTaskProofs`. This is the evidence accumulation mechanism — each completed task verified by an issuer adds to the proof count.

**Lines 288–309 — revokeCredential.** Either the original issuer or the owner can revoke. Sets `active = false`, clears `agentCapability[agent][hash] = 0` (the reverse lookup), decrements `activeCredentials` counter.

**Lines 331–336 — hasActiveCredential.** Checks `credId != 0`, `cred.active`, and `expiresAt == 0 || expiresAt > block.timestamp`. This is the canonical "does this agent currently have this capability?" query.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `identityRegistry` | `IIdentityRegistry_CC` | Agent activity check | `immutable` |
| `owner` | `address` | Admin | `immutable` |
| `_credentialCounter` | `uint256` | Monotonic ID | Incremented on issue |
| `_credentials` | `mapping(uint256→Credential)` | Credential records | Written on issue, active flag on revoke |
| `agentProfiles` | `mapping(address→AgentCredentialProfile)` | Aggregate stats | Updated on issue/revoke/proof |
| `agentCapability` | `mapping(address→mapping(bytes32→uint256))` | Capability → credentialId lookup | Set on issue, cleared on revoke |
| `_agentCredentials` | `mapping(address→uint256[])` | Agent's credential history | Appended on issue |
| `approvedIssuers` | `mapping(address→bool)` | Authorized issuers | Owner-controlled |
| `_capabilityHolders` | `mapping(bytes32→address[])` | Agents with a capability | Appended on issue |

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `CredentialIssued` | `issueCredential` | credentialId, agent, capabilityHash, issuedBy, expiresAt, timestamp |
| `CredentialRevoked` | `revokeCredential` | credentialId, agent, capabilityHash, timestamp |
| `TaskProofAdded` | `addTaskProof` | credentialId, agent, capabilityHash, newProofCount, timestamp |
| `IssuerApproved` | `approveIssuer` | issuer, timestamp |
| `IssuerRemoved` | `removeIssuer` | issuer, timestamp |

### Security Invariants

1. Only registered agents receive credentials
2. Only one active credential per agent per capability at a time
3. Only approved issuers can issue credentials or add task proofs
4. Only original issuer or owner can revoke
5. Expiry is enforced in `hasActiveCredential` and `addTaskProof`
6. Reentrancy blocked on `issueCredential`, `addTaskProof`, `revokeCredential`
7. `_capabilityHolders` array unbounded — high adoption could cause OOG in full iteration

### Integration

`VaultfireCapabilityCredentials` feeds the task routing layer. When a task is posted with a required capability hash, off-chain routers call `getCapabilityHolders(capabilityHash)` or `hasActiveCredential(agent, hash)` to find qualified agents. Higher `taskProofCount` correlates with more verifiable experience.

---

## 12. VaultfireReputationStaking — `theloopbreaker-site/contracts/VaultfireReputationStaking.sol`

**Category:** Reputation staking (agent-to-agent vouching with economic stake)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
19  interface IIdentityRegistry { isAgentActive(address) → bool }
27  contract VaultfireReputationStaking {
33–36  enum VouchStatus { Active, Withdrawn, Slashed }
39–46  struct Vouch { id, voucher, vouchedAgent, stakeAmount, createdAt, status }
48–54  struct AgentVouchProfile { totalVouchesReceived, totalStakeReceived,
         totalVouchesGiven, totalStakeGiven, slashCount }
61  uint256 public constant MIN_VOUCH_STAKE = 0.001 ether
64  uint256 public constant MIN_LOCK_PERIOD = 7 days
70  IIdentityRegistry public immutable identityRegistry
71  address public immutable owner
77  uint256 private _vouchCounter
78  mapping(uint256 => Vouch) private _vouches
79  mapping(address => AgentVouchProfile) public profiles
82  mapping(address => mapping(address => uint256)) public activeVouch
85  mapping(address => uint256[]) private _vouchesReceived
88  mapping(address => uint256[]) private _vouchesGiven
94–103 Manual ReentrancyGuard
109–131 Events: VouchCreated, VouchWithdrawn, VouchSlashed
137–141 constructor
147–158 Modifiers: onlyRegisteredAgent, onlyOwner
164–212 vouch(vouchedAgent) payable: validates, stores vouch, updates profiles
219–246 withdrawVouch(vouchId): 7-day lock, refunds ETH
255–281 slashAgent(agent): onlyOwner, slashes ALL active vouches
287–323 View: getVouch, getVouchesReceived, getVouchesGiven, getProfile, vouchCount
```

### Line-by-Line Walkthrough

**Lines 61–64 — Constants.** Minimum stake is 0.001 ETH (keeps vouching meaningful). `MIN_LOCK_PERIOD = 7 days` prevents flash-stake-and-withdraw attacks where a voucher quickly exits after staking.

**Line 82 — activeVouch.** `voucher → vouchedAgent → vouchId`. Zero means no active vouch. Enforces that a voucher can only have one active vouch per agent at a time.

**Lines 164–212 — vouch.** `payable`, `nonReentrant`, `onlyRegisteredAgent`. Cannot vouch for self. Cannot vouch for zero address. Both voucher and vouchedAgent must be active agents. Cannot vouch for someone already being actively vouched for (`activeVouch[msg.sender][vouchedAgent] == 0` check). Updates both parties' `AgentVouchProfile` stats.

**Lines 219–246 — withdrawVouch.** Validates the 7-day lock period: `block.timestamp >= createdAt + MIN_LOCK_PERIOD`. Sets status to `Withdrawn`, clears `activeVouch`. Decrements profile stats on both sides. Sends ETH back via `.call{value}("")`.

**Lines 255–281 — slashAgent.** `onlyOwner`. Iterates ALL vouches received by the agent (`_vouchesReceived[agent]`). For each active vouch: sets to `Slashed`, adds stake to `totalSlashed`, clears `activeVouch`, decrements voucher's profile. The slashed ETH remains in the contract — no withdrawal mechanism provided for owner to extract it. Also resets `totalVouchesReceived = 0` and `totalStakeReceived = 0` on the slashed agent's profile.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `MIN_VOUCH_STAKE` | `uint256` constant | Minimum ETH per vouch | Immutable |
| `MIN_LOCK_PERIOD` | `uint256` constant | 7 days lock before withdrawal | Immutable |
| `identityRegistry` | `IIdentityRegistry` | Agent verification | `immutable` |
| `owner` | `address` | Slash authority | `immutable` |
| `_vouchCounter` | `uint256` | Monotonic vouch ID | Incremented on `vouch` |
| `_vouches` | `mapping(uint256→Vouch)` | Vouch records | Status updated on withdraw/slash |
| `profiles` | `mapping(address→AgentVouchProfile)` | Aggregate stats | Updated on vouch/withdraw/slash |
| `activeVouch` | `mapping(address→mapping(address→uint256))` | Current vouch per pair | Set on vouch, cleared on withdraw/slash |
| `_vouchesReceived` | `mapping(address→uint256[])` | Received vouch IDs | Append-only |
| `_vouchesGiven` | `mapping(address→uint256[])` | Given vouch IDs | Append-only |

### Math — Slashing

**On `slashAgent(agent)`:**
- For each active vouch received by `agent`, the full `stakeAmount` is slashed
- Slashed ETH accumulates in the contract (no withdrawal path)
- `agent.profiles.totalVouchesReceived` and `totalStakeReceived` reset to 0
- `agent.profiles.slashCount` incremented

**Voucher's profile update on slash:**
- `totalVouchesGiven -= 1`
- `totalStakeGiven -= v.stakeAmount`

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `VouchCreated` | `vouch` | vouchId, voucher, vouchedAgent, stakeAmount, timestamp |
| `VouchWithdrawn` | `withdrawVouch` | vouchId, voucher, vouchedAgent, stakeReturned, timestamp |
| `VouchSlashed` | `slashAgent` (per vouch) | vouchId, voucher, vouchedAgent, stakeSlashed, timestamp |

### Security Invariants

1. Minimum 7-day lock prevents flash-stake attacks
2. One active vouch per pair at a time
3. Only owner can slash — governance must be the owner in production
4. Slashed ETH is locked in contract — no rug-pull path
5. Only registered agents can vouch
6. Reentrancy blocked on `vouch`, `withdrawVouch`, `slashAgent`
7. `_vouchesReceived` array is unbounded — `slashAgent` cost is O(n) where n = vouches received

### Integration

`VaultfireReputationStaking` provides an economic backing layer for the Vaultfire trust graph. High `totalStakeReceived` indicates community confidence in an agent. The `TrustPortabilityExtensionV3` syncs trust tiers that are computed using reputation data (including vouch counts and stake totals) from this contract.

---

## 13. VaultfireTrustOracle — `theloopbreaker-site/contracts/VaultfireTrustOracle.sol`

**Category:** Trust oracle (verifiable performance attestations)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
19  interface IIdentityRegistry_TO { isAgentActive(address) → bool }
27  contract VaultfireTrustOracle {
33–43  struct Attestation { id, attester, agent, taskId, qualityScore, categoryHash,
         evidenceHash, timestamp, verified }
45–51  struct AgentPerformance { totalAttestations, averageQuality, verifiedAttestations,
         taskCompletions, lastUpdated }
58  uint256 private constant MAX_FOR_TRUE_AVG = 1000
64  IIdentityRegistry_TO public immutable identityRegistry
70–82  State: _attestationCounter, _attestations, performance, _agentAttestations,
         _attesterAttestations, _hasAttested
87–96  Manual ReentrancyGuard
102–118 Events: AttestationSubmitted, PerformanceUpdated
124–127 constructor
142–202 submitAttestation: validates agent!=self, score 0-10000, no double-attest per task,
          determines verified flag, updates performance
208–242 _updatePerformance: true average up to 1000 attestations, EMA thereafter
248–287 View: getAttestation, getPerformance, getAgentAttestations, getAttesterAttestations,
          attestationCount
```

### Line-by-Line Walkthrough

**Line 38 — qualityScore.** Ranges 0–10,000 (basis points). 10,000 = perfect performance. This allows 1 basis point precision in quality scoring.

**Line 42 — verified flag.** Set to `identityRegistry.isAgentActive(msg.sender)` — if the attester is a registered agent, the attestation is "verified." Unregistered attesters can still submit (open system), but their attestations are marked `verified = false`.

**Line 58 — MAX_FOR_TRUE_AVG = 1000.** Below 1000 attestations, a true running average is computed. Above 1000, an exponential moving average (EMA with α = 10%) is used to prevent integer overflow and control storage costs.

**Lines 163–169 — Double-attestation prevention.** For task-linked attestations (`taskId > 0`), the `_hasAttested[attester][agent][taskId]` triple-key map prevents the same attester from submitting two scores for the same task-agent pair.

**Lines 208–242 — _updatePerformance (EMA after 1000):**
```
if totalAttestations < 1000:
    totalScore = averageQuality × totalAttestations
    totalScore += newScore
    totalAttestations += 1
    averageQuality = totalScore / totalAttestations  // true average
else:
    averageQuality = (averageQuality × 9 + newScore) / 10  // 10% EMA
    totalAttestations += 1
```

The EMA constant of 10% weights recent scores more heavily, giving a decaying influence to old data.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `identityRegistry` | `IIdentityRegistry_TO` | Attester verification | `immutable` |
| `_attestationCounter` | `uint256` | Monotonic ID | Incremented on submit |
| `_attestations` | `mapping(uint256→Attestation)` | All attestation records | Append-only |
| `performance` | `mapping(address→AgentPerformance)` | Aggregate performance per agent | Updated on submit |
| `_agentAttestations` | `mapping(address→uint256[])` | Attestation IDs received | Append-only |
| `_attesterAttestations` | `mapping(address→uint256[])` | Attestation IDs given | Append-only |
| `_hasAttested` | `mapping(address→mapping(address→mapping(uint256→bool)))` | Double-attest guard | Set on submit |

### Math — Running Average

**True average (n < 1000):**
```
avg_new = (avg_old × n + newScore) / (n + 1)
```

**EMA (n ≥ 1000):**
```
avg_new = (avg_old × 9 + newScore) / 10
```
Equivalent to α = 0.1, effective window ≈ 10 recent attestations at steady state.

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `AttestationSubmitted` | `submitAttestation` | attestationId, attester, agent, qualityScore, categoryHash, taskId, verified, timestamp |
| `PerformanceUpdated` | `_updatePerformance` | agent, averageQuality, totalAttestations, timestamp |

### Security Invariants

1. Cannot attest for self
2. Score must be ≤ 10,000
3. No double-attestation per attester-agent-task triple
4. Agent must be registered for attestation to be accepted
5. Attestations from unregistered attesters accepted but flagged `verified = false`
6. Reentrancy blocked on `submitAttestation`
7. `_agentAttestations` array unbounded — O(n) read for high-volume agents

### Integration

The trust oracle is the off-chain performance history bridge. `VaultfireTaskEscrow` task completions feed into `VaultfireCapabilityCredentials` via `addTaskProof`, and the task poster can separately call `submitAttestation` here to record quality scores. The `TrustPortabilityExtensionV3` SYNC_TRUST_TIER payload includes `averageRating` and `verifiedFeedbacks` fields that map to this contract's `averageQuality` and `verifiedAttestations`.

---

## 14. VaultfireForumRegistry — `theloopbreaker-site/contracts/VaultfireForumRegistry.sol`

**Category:** Forum registry (XMTP MLS group discovery)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
  4–30  NatSpec: lightweight permissionless registry for XMTP MLS groupIds;
          first-write-wins, publisher-can-rotate, stateless, no PII
31  contract VaultfireForumRegistry {
33–38  struct Entry { bytes groupId; address publisher; uint256 publishedAt; uint256 updatedAt }
41  mapping(string => Entry) private _entries
44  string[] private _categories
46–61  Events: GroupRegistered, GroupRotated
71–95  registerGroup(category, groupId): first-write-wins or rotation by publisher
97–111 getGroup(category) → (groupId, publisher, publishedAt, updatedAt)
113–116 isRegistered(category) → bool
118–121 listCategories() → string[]
123–126 categoryCount() → uint256
```

### Line-by-Line Walkthrough

**Lines 33–38 — Entry struct.** Stores the raw XMTP MLS `groupId` as `bytes` (variable-length). `publisher` is set at first registration and cannot be changed. `publishedAt` and `updatedAt` separate first registration from rotations.

**Lines 46–61 — Events.** `GroupRegistered` emits the category string both as an indexed `categoryHashIndexed` (for filter) and as a non-indexed `category` (for reading). `GroupRotated` includes both old and new `groupId`.

**Lines 71–95 — registerGroup.** Input validation: category 1–64 bytes, groupId 1–256 bytes. Branch:
1. `e.publisher == address(0)`: fresh registration — sets publisher, timestamps, pushes to `_categories` array, emits `GroupRegistered`
2. `e.publisher != address(0) && e.publisher == msg.sender`: rotation — updates `groupId` and `updatedAt`, emits `GroupRotated`
3. `e.publisher != address(0) && e.publisher != msg.sender`: reverts with "not original publisher"

There is no mechanism for the publisher to transfer ownership of a category.

**Lines 113–126 — View functions.** `listCategories()` returns the full string array — could be large with many categories. `isRegistered` is O(1) via the mapping.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `_entries` | `mapping(string→Entry)` | Category → group mapping | Written on register/rotate |
| `_categories` | `string[]` | Registration-order category list | Appended on first registration |

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `GroupRegistered` | First `registerGroup` call | categoryHashIndexed, category, groupId, publisher, timestamp |
| `GroupRotated` | Subsequent call by publisher | categoryHashIndexed, category, oldGroupId, newGroupId, publisher, timestamp |

### Security Invariants

1. First-write-wins — prevents drive-by category squatting
2. Only original publisher can rotate (no admin override)
3. No reentrancy risk — no ETH, no external calls
4. No access control, no upgradeability — "just a map"
5. `_categories` array grows without bound
6. `listCategories()` is O(n) and could hit gas limits with many categories

### Integration

`VaultfireForumRegistry` bridges the on-chain Vaultfire ecosystem to XMTP off-chain messaging. The forum frontend reads category-to-groupId mappings here. When a new XMTP MLS group is created for a discussion category, the creator registers the groupId here so new users can discover and join without a centralized coordination server.

---

## 15. VaultfireBondInsurancePool — `theloopbreaker-site/contracts/VaultfireBondInsurancePool.sol`

**Category:** Bond insurance pool

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
19  interface IIdentityRegistry_BIP { isAgentActive(address) → bool }
27  contract VaultfireBondInsurancePool {
33–37  enum ClaimStatus { Pending, Approved, Denied }
39–43  struct Contribution { contributor, amount, timestamp }
45–55  struct InsuranceClaim { id, claimant, amount, reason, evidenceHash, status,
         payout, createdAt, resolvedAt }
57–62  struct ContributorProfile { totalContributed, contributionCount, insured,
         firstContributionAt }
69  uint256 public constant MIN_CONTRIBUTION = 0.001 ether
72  uint256 public constant MIN_FOR_INSURED = 0.01 ether
75  uint256 public constant MAX_CLAIM_PERCENT = 1000; // 10% in basis points
83  IIdentityRegistry_BIP public immutable identityRegistry
84  address public immutable owner
90–100 State: totalPoolBalance, totalContributors, totalClaimsCount, totalPaidOut,
          _claimCounter, _claims, contributors, _contributorHistory, _allContributions
106–115 Manual ReentrancyGuard
120–143 Events: ContributionMade, ClaimSubmitted, ClaimResolved
149–153 constructor
159–170 Modifiers: onlyRegisteredAgent, onlyOwner
181–219 contribute(): payable, updates profile, grants Insured badge at MIN_FOR_INSURED
228–269 submitClaim(): validates max payout, requires insured status
278–313 resolveClaim(): onlyOwner, approves/denies, transfers ETH on approval
319–357 View functions
```

### Line-by-Line Walkthrough

**Lines 69–75 — Constants.** `MIN_CONTRIBUTION = 0.001 ETH`. `MIN_FOR_INSURED = 0.01 ETH` — requires 10× the minimum contribution to earn Insured status. `MAX_CLAIM_PERCENT = 1000 BPS = 10%` — a single claim cannot exceed 10% of the pool at claim submission time.

**Lines 181–219 — contribute.** `payable`, `nonReentrant`, `onlyRegisteredAgent`. First contribution per address increments `totalContributors`. Insured badge is granted once `totalContributed >= 0.01 ETH` — permanent once earned. Contributions are both added to `contributors[msg.sender]` profile and appended to `_allContributions`.

**Lines 228–269 — submitClaim.** Requires the caller to already be insured. Cap check:
```solidity
uint256 maxPayout = (totalPoolBalance * MAX_CLAIM_PERCENT) / BPS_DENOM;
require(amount <= maxPayout, "claim exceeds max payout");
```
This is calculated at time of submission — if the pool shrinks between submission and resolution, the 10% cap is not re-checked at resolution time.

**Lines 278–313 — resolveClaim.** `onlyOwner`. Conditional: on approval, validates payout ≤ requested and ≤ pool balance, deducts from `totalPoolBalance`, sends ETH via `.call{value}`. On denial, sets status to `Denied` with zero payout.

### Math — Insurance Limits

```
maxPayout = totalPoolBalance × 1000 / 10000 = totalPoolBalance × 10%
```

If pool has 10 ETH: max single claim = 1 ETH. This prevents a single claim from catastrophically draining the pool.

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `ContributionMade` | `contribute` | contributor, amount, totalContributed, insuredStatus, timestamp |
| `ClaimSubmitted` | `submitClaim` | claimId, claimant, requestedAmount, reason, timestamp |
| `ClaimResolved` | `resolveClaim` | claimId, claimant, status (Approved/Denied), payout, timestamp |

### Security Invariants

1. Only insured contributors can file claims
2. Claims capped at 10% of pool at submission time
3. Payout cannot exceed what was claimed or what the pool holds
4. Only owner can resolve claims
5. Reentrancy blocked on contribute, submitClaim, resolveClaim
6. No withdrawal for non-claim ETH — slashed vouch ETH accumulates here (see `VaultfireReputationStaking`)
7. `_allContributions` array unbounded

### Integration

`VaultfireBondInsurancePool` creates a collective risk-sharing layer for the bond ecosystem. Contributors who earn Insured status can file claims if bonded tasks fail. The "Insured" badge in `ContributorProfile.insured` is visible via `isInsured(agent)` and can be read by task routing systems to prefer insured workers for high-value tasks.

---

## 16. TrustPortabilityExtensionV3 (push) — `push_contracts_v3_TrustPortabilityExtensionV3.sol`

**Category:** Trust oracle / cross-chain trust portability (V3.1 partner-ready, pre-V3.3 audit)

### Overview

This is the V3.1 "partner-ready" version of the cross-chain trust portability layer. It implements `ITeleporterReceiver` from Avalanche's Interchain Messaging (ICM) protocol and sends trust data through Teleporter or via an off-chain relayer path. Three message types are supported:

- `SYNC_TRUST_TIER` — trust tier + raw score + metadata, synced with expiry
- `SYNC_VNS_IDENTITY` — VNS name ↔ address binding with identity type
- `SYNC_ZK_ATTESTATION` — RISC Zero-verified belief attestation results

**V3.1 key features:**
- Unbounded array caps: `MAX_SYNCED_ENTRIES = 10,000` and `MAX_ATTESTATIONS_PER_PROVER = 1,000`
- `SyncedCapReached` event when an array hits its cap
- All V2 security features preserved (replay protection via `processedMessages`, pause, relayer ACL, expiry checks)

**Missing vs V3.3 (workspace root variant):**
- No `relayerCount` tracking (HIGH-20)
- No `trustedVerifierImageIds` registry (HIGH-07)
- No `remoteBridgeSigner` ECDSA signing requirement on `relayTrustMessage` (NEW-01)
- `sendTrustTier` does not validate tier/score consistency (HIGH-06)
- `_handleTrustTier` does not re-validate tier/score on destination

### Message Flow

**Outbound (source chain):**
1. Authorized relayer calls `sendTrustTier` / `sendVNSIdentity` / `sendZKAttestation`
2. Payload packed into `TrustTierPayload` / `VNSIdentityPayload` / `ZKAttestationPayload`
3. Wrapped in `TrustMessage` envelope with nonce, timestamp, sender, sourceChainId
4. Encoded via `abi.encode`, sent via Teleporter if messenger configured
5. `TrustMessageSent` event emitted

**Inbound (destination chain):**
1. Teleporter delivers to `receiveTeleporterMessage` (validates source chain ID and origin address)
2. Or authorized relayer calls `relayTrustMessage` (no signature validation in V3.1)
3. `_processTrustMessage` decodes, checks `processedMessages[msgHash]` replay guard
4. Dispatches to `_handleTrustTier` / `_handleVNSIdentity` / `_handleZKAttestation`
5. Each handler stores payload and emits corresponding sync event

### TrustTier Score Mapping

```
Unverified    : score  0–14
Registered    : score 15–39
Trusted       : score 40–59
HighlyTrusted : score 60–79
Exemplary     : score 80–100
```

Tier expiry is checked in `isTrustTierRecognized`: if `expiresAt > 0 && block.timestamp > expiresAt`, returns `(false, Unverified, 0)`.

### State Variables (key)

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `owner` | `address` | Admin | Changed by `transferOwnership` |
| `paused` | `bool` | Emergency brake | `pause`/`unpause` |
| `outboundNonce` | `uint256` | Monotonic outbound message counter | Incremented per send |
| `teleporterMessenger` | `address` | Avalanche ICM messenger | `setTeleporterMessenger` |
| `remoteBlockchainID` | `bytes32` | Avalanche blockchain ID of remote | `setRemoteBridge` |
| `remoteBridgeAddress` | `address` | Remote chain's bridge contract | `setRemoteBridge` |
| `remoteChainId` | `uint256` | EVM chainId of remote | `setRemoteBridge` |
| `authorizedRelayers` | `mapping(address→bool)` | Whitelisted relayers | `addRelayer`/`removeRelayer` |
| `processedMessages` | `mapping(bytes32→bool)` | Replay protection | Set on receive |
| `syncedTrustTiers` | `mapping(address→TrustTierPayload)` | Received trust tiers | `_handleTrustTier` |
| `syncedVNSByNameHash` | `mapping(bytes32→VNSIdentityPayload)` | Name → VNS payload | `_handleVNSIdentity` |
| `syncedVNSByAddress` | `mapping(address→bytes32)` | Address → name hash | `_handleVNSIdentity` |
| `syncedZKAttestations` | `mapping(bytes32→mapping(address→ZKAttestationPayload))` | Belief → prover attestation | `_handleZKAttestation` |

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `TrustTierSynced` | `_handleTrustTier` | subject, tier, score, sourceChainId, expiresAt |
| `VNSIdentitySynced` | `_handleVNSIdentity` | owner, name, nameHash, identityType, sourceChainId |
| `ZKAttestationSynced` | `_handleZKAttestation` | prover, beliefHash, zkVerified, sourceChainId |
| `TrustMessageSent` | `_sendTrustMessage` | nonce, messageType, messageHash, destChainId |
| `TrustMessageReceived` | `_processTrustMessage` | nonce, messageType, messageHash, sourceChainId |
| `SyncedCapReached` | Array cap hit | arrayName, cap |
| `RelayerAdded/Removed` | Admin | relayer address |

### Security Invariants

1. Replay protection via `processedMessages[keccak256(encoded)]`
2. Source chain validation (`sourceBlockchainID == remoteBlockchainID`)
3. Origin sender validation on Teleporter path (`originSenderAddress == remoteBridgeAddress`)
4. Array growth capped at 10,000 / 1,000 per prover
5. Tier expiry prevents stale data
6. **V3.1 gap:** `relayTrustMessage` accepts any authorized relayer without signature verification
7. **V3.1 gap:** No zkVerified image ID whitelist

### Integration

Deployed as a pair (one per chain). The Base instance sends to Avalanche and vice versa. Configured via `setRemoteBridge` with the counterpart's Avalanche blockchain ID, address, and EVM chain ID. `TrustPortabilityExtensionV3.isTrustTierRecognized(agent)` is the canonical query for cross-chain trust recognition.

---

## 17. TrustPortabilityExtensionV3 (workspace root) — `TrustPortabilityExtensionV3.sol`

**Category:** Trust oracle / cross-chain trust portability (V3.3 — fully patched)

### Overview

This is the fully patched V3.3 production variant with three audit remediations applied on top of V3.1:

**HIGH-06 (tier/score consistency on destination):** `_handleTrustTier` now re-validates that `tier` matches `score` using the same ranges as `sendTrustTier`. This defends against a fabricated payload that encodes a mismatched (tier, score) pair after a CRIT-02 relayer compromise.

**HIGH-07 (trusted ZK verifier registry):** New state variable `trustedVerifierImageIds: mapping(bytes32→bool)`. `_handleZKAttestation` now requires that any payload with `zkVerified=true` carries a `verifierImageId` that is in the whitelist. New admin functions `addTrustedVerifierImageId(imageId)` and `removeTrustedVerifierImageId(imageId)`.

**HIGH-20 (last relayer guard):** New state variable `relayerCount: uint256`. `addRelayer` increments it. `removeRelayer` guards `require(relayerCount > 1, "cannot remove last relayer")`.

**NEW-01 (relayer path ECDSA signing):** `relayTrustMessage` now accepts a second argument `bytes calldata signature`. It requires `remoteBridgeSigner != address(0)`. It hashes the message with EIP-191 prefix and recovers the signer via `ecrecover`. New state variables: `remoteBridgeSigner: address`, `relayerMessageHashes: mapping(bytes32→bool)` (secondary replay guard on relayer path specifically).

**MEDIUM-TPE-01:** `_handleVNSIdentity` now clears the old owner's reverse mapping (`syncedVNSByAddress[existing.owner]`) when a name transfers to a new owner.

All V3.1 features preserved. The key behavioral difference is the `relayTrustMessage` signature now has two parameters rather than one.

### Signature Verification (NEW-01)

```solidity
bytes32 ethSignedHash = keccak256(
    abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)
);
// assembly extracts r, s, v from calldata
address recovered = ecrecover(ethSignedHash, v, r, s);
require(recovered != address(0) && recovered == remoteBridgeSigner, "invalid bridge signature");
```

This mirrors the CRIT-02 fix in `VaultfireTeleporterBridgeV3`, ensuring the off-chain relayer cannot inject arbitrary trust data — the remote bridge must sign each message with its private key.

### Additional State vs V3.1

| Variable | Type | Added For |
|---|---|---|
| `relayerCount` | `uint256` | HIGH-20 — last relayer guard |
| `trustedVerifierImageIds` | `mapping(bytes32→bool)` | HIGH-07 — ZK verifier whitelist |
| `remoteBridgeSigner` | `address` | NEW-01 — relayer path authentication |
| `relayerMessageHashes` | `mapping(bytes32→bool)` | NEW-01 — secondary replay guard |

### Additional Events vs V3.1

| Event | Added For |
|---|---|
| `TrustedVerifierImageIdAdded` | HIGH-07 |
| `TrustedVerifierImageIdRemoved` | HIGH-07 |
| `RemoteBridgeSignerSet` | NEW-01 |

---

## 18. TrustDataBridge — `theloopbreaker-site/contracts-draft/TrustDataBridge.sol`

**Category:** Trust oracle / cross-chain bridge (draft — permissioned relayer model without Teleporter)

### Overview

`TrustDataBridge` is a **draft** contract implementing a simpler cross-chain trust synchronization mechanism without Avalanche Teleporter. It covers ETH Mainnet ↔ Base and ETH Mainnet ↔ Avalanche paths via a permissioned relayer network. Unlike `TrustPortabilityExtensionV3`, it does not implement `ITeleporterReceiver` and uses a separate emit/receive pattern rather than message envelopes.

**Supported data types:**
1. `IdentityAttestation` — from `ERC8004IdentityRegistry`
2. `ReputationScore` — from `VaultfireERC8004Adapter`
3. `VNSRegistration` — VNS name ↔ address
4. `AccountabilityBond` — from `AIAccountabilityBondsV2`
5. `PartnershipBond` — from `AIPartnershipBondsV2`

**Status:** `@custom:status DRAFT — Pending audit before mainnet activation`

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
 34  contract TrustDataBridge {
 41–47  enum DataType { IdentityAttestation, ReputationScore, VNSRegistration,
          AccountabilityBond, PartnershipBond }
 50–58  struct TrustRecord { dataType, subject, dataHash, payload, sourceChainId, timestamp, nonce }
 64  address public owner; bool public paused
 68  uint256 public outboundNonce
 71  mapping(uint256 => uint256) public inboundNonce  // sourceChainId → highest processed nonce
 74  mapping(bytes32 => bool) public processed         // dataHash dedup
 77  mapping(address => bool) public relayers; uint256 public relayerCount
 79  uint256 public requiredRelayers                    // quorum
 82  mapping(uint256 => bool) public supportedChains
 88–104 Storage mappings for 5 data types
107–108 receivedCount[uint8] and emittedCount[uint8]
113–139 Events: TrustDataEmitted (5 fields indexed), TrustDataReceived,
          RelayerAdded/Removed, ChainAdded/Removed, Paused/Unpaused
145–158 Modifiers: onlyOwner, onlyRelayer, whenNotPaused
164–176 constructor: owner=deployer, supports chains 1/8453/43114, owner is first relayer
189–327 emit functions: emitIdentityAttestation, emitReputationScore, emitVNSRegistration,
          emitAccountabilityBond, emitPartnershipBond — all permissionless, all emit TrustDataEmitted
332–384 receiveTrustData: onlyRelayer, validates hash, stores by type
393–444 View functions: has* and get* per data type, getStats
465–508 Admin: addRelayer, removeRelayer, setRequiredRelayers, addChain, removeChain,
          pause, unpause, transferOwnership
514–522 receive() and fallback() — both revert ("no ETH accepted")
```

### Line-by-Line Walkthrough

**Lines 189–327 — Emit functions (permissionless).** Any address (not just registered agents) can emit trust data for relay to another chain. This is intentional: the emit side is just an event log — the actual binding storage happens on `receiveTrustData`. Emit functions compute `dataHash = keccak256(abi.encodePacked(dataType, subject, payload, block.chainid))` and increment `outboundNonce`.

**Lines 332–384 — receiveTrustData (relayer-gated).** Validates:
1. `!processed[dataHash]` — deduplication
2. `subject != address(0)`
3. `supportedChains[sourceChainId]`
4. `nonce >= inboundNonce[sourceChainId]` — rejects stale nonces, allows out-of-order (nonce is a minimum bound, not strict sequence)
5. Hash integrity: recomputes `expectedHash` from the payload and verifies against supplied `dataHash`

Sets `processed[dataHash] = true` and `inboundNonce[sourceChainId] = nonce + 1`. Note: single-relayer quorum — current `requiredRelayers = 1` means one relayer can unilaterally write trust data. The `requiredRelayers` field exists but there is no multi-signature accumulation logic — this is a noted gap for production hardening.

**Lines 354–379 — VNS registration storage.** For `DataType.VNSRegistration`, the storage key is derived as `keccak256(payload)` rather than `subject` — because VNS names have a many-to-one relationship with addresses.

**Lines 465–478 — removeRelayer.** Guards `relayerCount > requiredRelayers` — prevents removing so many relayers that quorum becomes unreachable.

**Lines 514–522 — receive/fallback.** Both explicitly revert, preventing ETH/token accumulation. This is a safety design choice documented in NatSpec.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `owner` | `address` | Admin | `transferOwnership` |
| `paused` | `bool` | Emergency pause | `pause`/`unpause` |
| `outboundNonce` | `uint256` | Monotonic outbound counter | Incremented per emit |
| `inboundNonce` | `mapping(uint256→uint256)` | Highest processed nonce per source chain | Updated per receive |
| `processed` | `mapping(bytes32→bool)` | Processed data hashes | Set on receive |
| `relayers` | `mapping(address→bool)` | Authorized relayers | Admin-controlled |
| `requiredRelayers` | `uint256` | Quorum for receiving data | `setRequiredRelayers` |
| `supportedChains` | `mapping(uint256→bool)` | Allowed source/dest chain IDs | Admin-controlled |
| Storage maps for 5 data types | Various | Received payloads | Written on `receiveTrustData` |

### Events

| Event | Trigger | Key Data |
|---|---|---|
| `TrustDataEmitted` | Each emit function | dataType (indexed), subject (indexed), dataHash, payload, destChainId, nonce, timestamp |
| `TrustDataReceived` | `receiveTrustData` | dataType (indexed), subject (indexed), dataHash, sourceChainId (indexed), nonce, timestamp |
| `RelayerAdded/Removed` | Admin | relayer address |
| `ChainAdded/Removed` | Admin | chainId |
| `Paused/Unpaused` | Admin | by address |

### Security Invariants

1. Hash integrity checked on receive — prevents data tampering
2. Deduplication via `processed[dataHash]`
3. Nonce ordering enforced per source chain
4. Receive is relayer-gated; emit is permissionless
5. **DRAFT GAP:** `requiredRelayers` field exists but no multi-sig accumulation — single relayer can write
6. Contract cannot receive ETH or tokens — both fallback and receive revert
7. Pause mechanism for emergency suspension
8. Chain whitelist prevents cross-chain spam from unsupported chains

### Dependencies + Rebuild Recipe

- **Imports:** None
- **Constructor:** no args
- **Pre-deployment config:** `addRelayer`, `setRequiredRelayers`, `addChain` for non-default chains
- **Status:** DRAFT — not production-ready without multi-relayer accumulation logic

---

## 19. IERC20Minimal — `vf-audit-contracts/IERC20Minimal.sol`

**Category:** Other (minimal ERC-20 interface)

### Full Source

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

/**
 * @title IERC20Minimal
 * @notice Minimal ERC20 interface for VaultFire V3.1 multi-asset staking.
 * @dev Only the functions actually used by BaseYieldPoolBondV3.
 *      Keeps dependency surface small for auditors.
 */
interface IERC20Minimal {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
}
```

### Description

A 4-function minimal ERC-20 interface. Deliberately omits `totalSupply`, `allowance`, and all ERC-20 optional extensions to minimize the auditable surface for `BaseYieldPoolBondV3`'s token interactions. The version in `vf-audit-contracts` adds the NatSpec note "for VaultFire V3.1 multi-asset staking."

All four functions mirror the EIP-20 specification exactly. `transferFrom` and `transfer` return `bool` to allow the caller to check for non-reverting failure modes (required because some tokens like USDT return false instead of reverting).

### Integration

Used by `BaseYieldPoolBondV3`, `AIAccountabilityBondsV3`, `AIPartnershipBondsV3`, and `VaultfireTaskEscrow` (which has its own inline `IERC20` definition with the same three functions).

---

## 20. IERC20Minimal — `v3-pr-ready/contracts/v3/IERC20Minimal.sol`

**Category:** Other (minimal ERC-20 interface — PR-ready variant)

Nearly identical to the audit-contracts variant. The NatSpec describes it as "for multi-asset staking" without the V3.1 qualifier. Identical four-function interface. Both files exist to provide the correct import path for their respective contract directory trees.

---

## 21. VaultfireErrors — `vf-audit-contracts/VaultfireErrors.sol`

**Category:** Other (shared custom error library)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
  4–14  NatSpec: custom errors reduce bytecode; 4-byte selectors vs 32+ bytes/string;
          prefixes: VF_=general/BaseYieldPool, AB_=AIAccountabilityBonds, PB_=AIPartnershipBonds
16–48  BaseYieldPoolBondV3 errors (VF_ prefix):
        VF_NativeOnly, VF_TokenAlreadyAccepted, VF_NameInvalid, VF_TokenNotAccepted,
        VF_TokenTransferFailed, VF_NativeTransferFailed, VF_MustSendValue, VF_AmountZero,
        VF_InsufficientYieldPool, VF_BelowMinimumBalance, VF_TransferFailed,
        VF_MinimumMustBePositive, VF_InsufficientNativeYield, VF_YieldPoolBelowMinimum,
        VF_InsufficientTokenYield, VF_NoAppreciationToDistribute,
        VF_InsufficientYieldPending, VF_InsufficientTokenYieldPend,
        VF_ExceedsProtocolFunds, VF_WithdrawalAlreadyPending, VF_NoWithdrawalPending,
        VF_TimelockNotExpired, VF_InsufficientProtocolFunds, VF_FundWithdrawalFailed,
        VF_TokenTransferApproval, VF_InsufficientBondStake
50–94  AIAccountabilityBondsV3 errors (AB_ prefix): 42 errors covering oracle registration,
        trust scores, metrics verification, distribution, challenge, slashing
96–128 AIPartnershipBondsV3 errors (PB_ prefix): 25 errors covering participants,
        distribution, oracle cross-check, identity, cycle confirmation
```

### Role in the Ecosystem

`VaultfireErrors.sol` is a top-level file (not a contract) that declares free-floating `error` definitions. Any contract in the V3 suite that `import`s this file gains access to all custom errors. This pattern:
1. Eliminates duplicate error definitions across contracts
2. Ensures all reverts use 4-byte selectors (vs. 32+ bytes per string)
3. Makes the error catalog discoverable in one file for auditors

**Gas savings:** Each `require(condition, "string")` costs at minimum 32 bytes of string data plus the require overhead. A custom `error SomeError()` costs 4 bytes (selector only). For a contract with 20 error conditions and 100-byte average strings, switching to custom errors saves ≈ 1,900 bytes of deployed bytecode.

Notable audit-fix additions at the end:
- `AB_OracleAlreadyConfirmed()` — [audit-v3.3] CRIT-08: prevents double-oracle confirmation
- `AB_InsufficientTokenProtocolFunds()` — [audit-v3.3] HIGH-04: ERC-20 challenge penalty gap
- `AB_OracleDeviationTooHigh()` — [audit-v3.3] CRIT-03: oracle cross-check failure
- `AB_ConfirmationStale()` — [audit-v3.3] HIGH-03: cycle-specific confirmation staleness
- `PB_InsufficientPartnershipFund()` — [audit-v3.3] HIGH-05: per-token fund gap
- `PB_NoPendingWithdrawal()` / `PB_WithdrawalTransferFailed()` — [audit-v3.3] HIGH-02: pull pattern
- `PB_AgentNotRegistered()` — [audit-v3.3] HIGH-16: identity check

### Integration

Imported by `BaseYieldPoolBondV3`, `AIAccountabilityBondsV3`, `AIPartnershipBondsV3`. The test-fixes variant (below) is identical in structure but may diverge in specific error additions over time.

---

## 22. VaultfireErrors — `test_fixes/contracts/VaultfireErrors.sol`

**Category:** Other (shared custom error library — test-fixes branch)

Identical structure and prefix conventions to `vf-audit-contracts/VaultfireErrors.sol`. This file exists in the `test_fixes` branch for use by the test-fixed versions of the V3 contracts. The audit-v3.3 additions (CRIT-08, HIGH-04, CRIT-03, HIGH-03, HIGH-05, HIGH-02, HIGH-16) are present in both files, indicating they were back-ported to the test branch after audit findings. No material differences detected.

---

## 23. IVaultfireBridgeNotifier — `vf-audit-contracts/IVaultfireBridgeNotifier.sol`

**Category:** Other (bridge notification interface)

### Full Source

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

/**
 * @title IVaultfireBridgeNotifier
 * @notice Minimal interface for bond contracts to notify the bridge of deactivations.
 * @dev [audit-v3.3] HIGH-18: Bond deactivation must notify the bridge so the remote
 *      chain marks the bond inactive and doesn't serve stale "active" state.
 */
interface IVaultfireBridgeNotifier {
    function notifyPartnershipBondDeactivated(uint256 bondId) external;
    function notifyAccountabilityBondDeactivated(uint256 bondId) external;
}
```

### Description

A 2-function interface that bond contracts call when a bond is deactivated. This addresses **audit finding HIGH-18**: without cross-chain notification, the remote chain's bridge continues serving the bond as "active" after it is deactivated on the source chain, creating a stale-state attack surface.

**Flow:**
1. Bond contract calls `deactivateBond(bondId)` on source chain
2. Within the same transaction, bond contract calls `IVaultfireBridgeNotifier(bridgeAddress).notifyPartnershipBondDeactivated(bondId)` or `notifyAccountabilityBondDeactivated(bondId)`
3. Bridge contract (implementing this interface) emits a deactivation event or sends a cross-chain message
4. Remote chain bridge marks the bond inactive

**Implementing contract:** `VaultfireTeleporterBridgeV3` is the expected implementer.

**Integration:** `AIPartnershipBondsV3` and `AIAccountabilityBondsV3` (in the audit-v3.3 versions) store a `bridgeNotifier: IVaultfireBridgeNotifier` address and call these functions on deactivation.

---

## 24. VaultfireInterface — `theloopbreaker-site/partner_modules/sdk/solidity/VaultfireInterface.sol`

**Category:** Other (partner SDK interface)

### Full Source

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface VaultfireInterface {
    function reward(address user, uint256 amount) external;
}
```

### Description

The minimal Solidity interface exposed to partner integrations via the SDK. A single function: `reward(address user, uint256 amount)`. Partner contracts that want to trigger Vaultfire reward distributions import this interface and call the deployed Vaultfire contract through it.

**Note:** Pragma is `^0.8.0` rather than `^0.8.25` — intended for broad partner compatibility.

**Integration:** This is the partner-facing entry point. The implementing contract (likely `VaultfireRewardStream` or a wrapper) handles the actual reward logic.

---

## 25. Deploy.s.sol — `theloopbreaker-site/risc0/script/Deploy.s.sol`

**Category:** Other (Foundry deployment script)

### Full Source Summary

```
  1  pragma solidity ^0.8.25;
  4  import "forge-std/Script.sol";
  5  import {ProductionBeliefAttestationVerifier} from "../src/ProductionBeliefAttestationVerifier.sol";
  7  /// @title DeployProductionVerifier
  9  /// Usage: forge script script/Deploy.s.sol:DeployProductionVerifier \
 11  ///          --rpc-url $BASE_RPC_URL --broadcast --verify
 14  ///          --etherscan-api-key $BASESCAN_API_KEY
 23  contract DeployProductionVerifier is Script {
 25  address constant BASE_MAINNET_VERIFIER =
 26      0x0b144e07a0826182b6b59788c34b32bfa86fb711;
 28  function run() external {
 30      uint256 deployerKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
 31      bytes32 imageId     = vm.envBytes32("IMAGE_ID");
 33      address verifierAddr = vm.envOr("RISC_ZERO_VERIFIER", BASE_MAINNET_VERIFIER);
 39      vm.startBroadcast(deployerKey);
 41      ProductionBeliefAttestationVerifier verifier =
 42          new ProductionBeliefAttestationVerifier(verifierAddr, imageId);
 44      vm.stopBroadcast();
 47      console.log("=== Deployment Complete ===");
 48      console.log("ProductionBeliefAttestationVerifier:", address(verifier));
 49      console.log("RiscZeroVerifier:", verifierAddr);
 51      console.logBytes32(imageId);
 52      console.log("Owner:", verifier.owner());
 53      console.log("Chain ID:", block.chainid);
 55  }
```

### Description

Foundry deployment script for `ProductionBeliefAttestationVerifier`. Not a deployable contract itself — inherits `Script` and is only run via `forge script`. Uses `vm.env*` cheatcodes to read three configuration values:

- `DEPLOYER_PRIVATE_KEY` — the deploying EOA's private key
- `IMAGE_ID` — the RISC Zero guest program image ID (bytes32 hex)
- `RISC_ZERO_VERIFIER` — optional override; defaults to `0x0b14...b711` (Base Mainnet RiscZeroVerifierRouter)

`vm.startBroadcast(deployerKey)` / `vm.stopBroadcast()` marks the section of code that generates actual broadcast transactions. Everything outside that block executes locally only.

**Hard-coded Base Mainnet verifier address:** `0x0b144e07a0826182b6b59788c34b32bfa86fb711`. This should be verified against the official RISC Zero deployment registry before production use.

### Rebuild Recipe

```bash
export DEPLOYER_PRIVATE_KEY=<hex>
export IMAGE_ID=<bytes32_hex>
export BASE_RPC_URL=https://mainnet.base.org
export BASESCAN_API_KEY=<key>

forge script script/Deploy.s.sol:DeployProductionVerifier \
  --rpc-url $BASE_RPC_URL \
  --broadcast \
  --verify \
  --etherscan-api-key $BASESCAN_API_KEY \
  -vvvv
```

---

## 26. SubmitProof.s.sol — `theloopbreaker-site/risc0/script/SubmitProof.s.sol`

**Category:** Other (Foundry proof submission script)

### Full Source Summary

```solidity
contract SubmitProof is Script {
    function run() external {
        uint256 deployerKey  = vm.envUint("DEPLOYER_PRIVATE_KEY");
        address verifierAddr = vm.envAddress("VERIFIER_ADDRESS");
        bytes memory seal    = vm.envBytes("PROOF_SEAL_HEX");
        bytes memory journal = vm.envBytes("PROOF_JOURNAL_HEX");
        
        ProductionBeliefAttestationVerifier verifier =
            ProductionBeliefAttestationVerifier(verifierAddr);
        
        vm.startBroadcast(deployerKey);
        bool success = verifier.verifyAttestation(seal, journal);
        vm.stopBroadcast();
        
        if (success) console.log("=== Attestation Verified Successfully ===");
        else console.log("=== Attestation Verification Failed ===");
    }
}
```

### Description

Foundry script to submit a RISC Zero proof to the deployed `ProductionBeliefAttestationVerifier`. Four environment variables required:

- `DEPLOYER_PRIVATE_KEY` — the submitting account
- `VERIFIER_ADDRESS` — deployed `ProductionBeliefAttestationVerifier` address
- `PROOF_SEAL_HEX` — hex-encoded seal bytes from the RISC Zero prover
- `PROOF_JOURNAL_HEX` — hex-encoded journal bytes from the RISC Zero prover

`verifier.verifyAttestation(seal, journal)` calls the on-chain ZK verifier. On success, it logs the attestation count. Note: the script exits silently on false return rather than reverting — callers should check logs carefully.

### Rebuild Recipe

```bash
export DEPLOYER_PRIVATE_KEY=<hex>
export VERIFIER_ADDRESS=0x...
export PROOF_SEAL_HEX=0x...
export PROOF_JOURNAL_HEX=0x...

forge script script/SubmitProof.s.sol:SubmitProof \
  --rpc-url $BASE_RPC_URL \
  --broadcast \
  -vvvv
```

---

## 27. consent\_contract.sol — `theloopbreaker-site/consent_contract.sol`

**Category:** Privacy guarantees (programmable consent tokens)

### Full Source Summary

```
  1  pragma solidity ^0.8.20;
  6  contract ConsentRegistry {
  7–17  struct ConsentToken { subject, purpose, dataClass, issuedAt, expiresAt,
           usageLimit, usageCount, revoked, ethicsHash }
 19–21  Events: ConsentIssued, ConsentRevoked, ConsentUsed
 23  mapping(uint256 => ConsentToken) private _tokens
 24  uint256 private _nextTokenId = 1
 25  address public immutable ethicsModule
 27–30  modifier onlySubject(uint256 tokenId)
 32–35  constructor(address ethicsModuleAddress)
 37–61  issueConsent(purpose, dataClass, expiresAt, usageLimit, ethicsHash) → tokenId
 63–68  revokeConsent(tokenId): onlySubject, sets revoked=true
 70–75  recordUsage(tokenId, payloadHash): validates + increments usageCount
 77–79  getConsent(tokenId) → ConsentToken
 81–101 isActionAllowed(tokenId, expectedPurpose, expectedDataClass, ethicsHash) → bool
103–117 _validateUsage(token) → bool: internal check for active/not-expired/not-over-limit
```

### Line-by-Line Walkthrough

**Lines 7–17 — ConsentToken struct.** Rich consent token with:
- `subject`: the address that issued consent (the data subject)
- `purpose`: human-readable purpose string (stored on-chain — note this differs from `PrivacyGuarantees.sol` which stores hashes only)
- `dataClass`: the class of data the consent covers
- `issuedAt` / `expiresAt`: uint64 timestamps
- `usageLimit` / `usageCount`: uint32 values enabling a consent token to be "used up" after N operations
- `ethicsHash`: bytes32 hash of the applicable ethics module state at time of issuance

**Line 25 — ethicsModule.** Immutable reference to an ethics compliance contract. Passed in constructor, never changed.

**Lines 37–61 — issueConsent.** Called by `msg.sender` (the data subject). Validates `expiresAt > block.timestamp` and `usageLimit > 0`. Auto-assigns `_nextTokenId++`. Emits `ConsentIssued` with purpose and dataClass strings (on-chain PII exposure — design trade-off vs. `PrivacyGuarantees` which uses hashes).

**Lines 63–68 — revokeConsent.** `onlySubject(tokenId)` — only the original subject can revoke. Sets `revoked = true`.

**Lines 70–75 — recordUsage.** Permissionless — any operator can record a usage event for a valid token. Validates via `_validateUsage`, increments `usageCount`, emits `ConsentUsed` with `payloadHash` (the specific data payload being accessed, hashed for privacy).

**Lines 81–101 — isActionAllowed.** The key gate function for operators. Checks:
1. `_validateUsage` passes
2. Purpose matches exactly (keccak comparison)
3. Data class matches exactly
4. `ethicsHash` matches the stored ethics hash

**Lines 103–117 — _validateUsage.** Internal. Returns false if subject is zero, token is revoked, block.timestamp > expiresAt, or usageCount >= usageLimit.

### State Variables

| Variable | Type | Purpose | Mutability |
|---|---|---|---|
| `_tokens` | `mapping(uint256→ConsentToken)` | Token storage | Written on issue, updated on revoke/usage |
| `_nextTokenId` | `uint256` | Monotonic token ID (starts at 1) | Incremented on issue |
| `ethicsModule` | `address` | Ethics compliance reference | `immutable` |

### Events

| Event | Trigger | Data |
|---|---|---|
| `ConsentIssued` | `issueConsent` | tokenId, subject, purpose, dataClass |
| `ConsentRevoked` | `revokeConsent` | tokenId, subject |
| `ConsentUsed` | `recordUsage` | tokenId, operator, payloadHash |

### Security Invariants

1. Only the subject can revoke their own consent
2. Usage limit is hard-enforced — once exhausted, `_validateUsage` returns false
3. Expiry is timestamp-based
4. `ethicsHash` must match exactly — binding issuance to a specific ethics state
5. **On-chain PII risk:** purpose and dataClass strings stored on-chain (contrast with `PrivacyGuarantees` which stores only hashes)
6. `recordUsage` is permissionless — any address can record usage against a valid token

### Relationship to PrivacyGuarantees

`consent_contract.sol` provides **programmable, usage-limited consent tokens** with rich on-chain metadata. `PrivacyGuarantees.sol` provides a simpler **consent flag registry** using hashed purposes. They are complementary: `ConsentRegistry` is appropriate for permissioned data access scenarios with usage tracking, while `PrivacyGuarantees` is a minimalist floor for all modules.

---

## 28. RewardStream.t.sol — `theloopbreaker-site/test/RewardStream.t.sol`

**Category:** Other (Foundry test contract — reentrancy validation)

### Full Source Summary

```
  1  pragma solidity ^0.8.20;
  4  import "forge-std/Test.sol";
  6  import {RewardStream} from "../contracts/RewardStream.sol";
  8  contract ReentrantReceiver {
  9      RewardStream private immutable _stream;
 10      bool private _blocked;
 12–14  constructor(RewardStream stream)
 16–26  receive() payable: attempts reentrancy on _stream.claimRewards,
          catches revert, sets _blocked=true
 28–30  blocked() external view → bool
 33  contract RewardStreamTest is Test {
 34      RewardStream private stream;
 35–36  constants: ADMIN=0xA11CE, GOVERNOR=0xBEEF
 38–42  setUp(): prank as ADMIN, deploys RewardStream(ADMIN, GOVERNOR), deals 5 ETH to stream
 44–55  testDistributeNoReentry: deploys ReentrantReceiver, queues + distributes 1 ETH,
           asserts receiver got 1 ETH, asserts blocked=true (attempted reentry)
```

### Description

A Foundry test that specifically validates reentrancy protection in `RewardStream.claimRewards`. The `ReentrantReceiver` contract attempts reentrancy by calling `claimRewards` in its `receive()` fallback. The test asserts:
1. The receiver ultimately received 1 ETH (the distribution succeeds)
2. The receiver's `_blocked` flag is true (reentrancy was attempted once — guarded by `_blocked` to avoid infinite loops)
3. The reentrancy `try` block caught a revert (either from reentrancy guard or "nothing to claim")

**Test pattern:** Foundry `vm.prank(ADMIN)` sets `msg.sender` for the next call. `vm.deal(address(stream), 5 ether)` directly loads the contract balance without a transfer call. `assertEq` and `assertTrue` are Foundry assertion functions.

**What it tests:** That `RewardStream.claimRewards` cannot be re-entered to double-claim rewards, and that the primary distribution path still succeeds even when the recipient tries reentrancy.

### Dependencies

- `forge-std/Test.sol` — Foundry test framework
- `RewardStream` — the contract under test (see `theloopbreaker-site/contracts/RewardStream.sol`)

---

## Cross-Contract Integration Map

```
MultisigGovernance / MultisigGovernanceV3
    ← is referenced by → VaultfireTaskEscrow.governance (dispute resolver)
    ← should own → MissionEnforcement.owner
    ← should own → AntiSurveillance.owner
    ← should own → VaultfireDisputeResolution.owner
    ← should own → VaultfireReputationStaking.owner (slash authority)
    ← should own → VaultfireBondInsurancePool.owner (claim resolver)
    ← should own → TrustPortabilityExtensionV3.owner

ERC8004IdentityRegistry (not in this batch)
    → isAgentActive() called by:
        VaultfireTaskEscrow (onlyRegisteredAgent)
        VaultfireDisputeResolution (onlyRegisteredAgent)
        VaultfireCapabilityCredentials (issueCredential guard)
        VaultfireReputationStaking (vouch, slashAgent)
        VaultfireBondInsurancePool (contribute, submitClaim)
        VaultfireTrustOracle (submitAttestation)

VaultfireTaskEscrow
    → issues task completions that feed into →
        VaultfireCapabilityCredentials.addTaskProof (via issuer)
        VaultfireTrustOracle.submitAttestation (via task poster)

VaultfireReputationStaking
    → profile data (totalStakeReceived, slashCount) feeds into →
        TrustPortabilityExtensionV3 (trust tier computation)

VaultfireTrustOracle
    → averageQuality, verifiedAttestations feed into →
        TrustPortabilityExtensionV3.sendTrustTier (averageRating, verifiedFeedbacks)

TrustPortabilityExtensionV3
    ← wraps → Avalanche ITeleporterMessenger for cross-chain sends
    → syncs to/from → remote chain TrustPortabilityExtensionV3 instance

IVaultfireBridgeNotifier
    → implemented by → VaultfireTeleporterBridgeV3 (Batch 7)
    → called by → AIPartnershipBondsV3 / AIAccountabilityBondsV3 on deactivation

MissionEnforcement
    ← verifyAIProfitCap() called by → AIAccountabilityBondsV2 / AIPartnershipBondsV2
    ← enforceMission modifier used by → bond contracts for principle gating

AntiSurveillance
    → surveillanceFreeOnly modifier available to → composing modules

PrivacyGuarantees
    → requiresConsent modifier available to → composing modules
    → checkDeletionRequest modifier available to → ERC8004 registries

VaultfireForumRegistry
    ← read by → Vaultfire Discussion Board frontend
    → no on-chain callers — view-only integration

VaultfireBondInsurancePool
    → isInsured(agent) readable by → task routing layer
    → funded by contributions + slashed ETH from VaultfireReputationStaking (indirectly)

IERC20Minimal / VaultfireErrors / IVaultfireBridgeNotifier
    → utility types imported by V3 bond contracts

VaultfireInterface (SDK)
    ← implemented by → VaultfireRewardStream
    → used by → partner module smart contracts
```

---

## Appendix — Rebuild Recipes

### MultisigGovernance (V1)
```solidity
constructor(address[] memory _signers, uint256 _threshold)
// No imports needed
```

### MultisigGovernanceV3 (V3, all timelock variants)
```solidity
// Import: @openzeppelin/contracts/utils/ReentrancyGuard.sol
constructor(address[] memory _signers, uint256 _threshold, address[3] memory _guardians)
// threshold must satisfy: threshold * 10000 / signers.length >= 5000
```

### MultisigGovernanceV3 (v3-pr-ready — Gnosis-style)
```solidity
// No imports
constructor(address[] memory _signers, uint256 _threshold)
// threshold must be >= 2, signers <= 20
```

### TrustPortabilityExtensionV3
```solidity
// Import: teleporter/ITeleporterMessenger.sol, teleporter/ITeleporterReceiver.sol
constructor(address _teleporterMessenger, uint256 _requiredGasLimit)
// Post-deploy: setRemoteBridge(), addRelayer() as needed
// V3.3 only: setRemoteBridgeSigner(), addTrustedVerifierImageId()
```

### VaultfireTaskEscrow
```solidity
constructor(
    address identityRegistry_,  // ERC8004IdentityRegistry
    address governance_,         // MultisigGovernance
    address usdc_,               // USDC token contract
    address feeRecipient_        // Protocol fee address
)
```

### VaultfireDisputeResolution
```solidity
constructor(address identityRegistry_)
// Owner is deployer; should transfer to MultisigGovernance
```

### VaultfireCapabilityCredentials
```solidity
constructor(address identityRegistry_)
// Owner is deployer; can approve additional issuers via approveIssuer
```

### VaultfireReputationStaking
```solidity
constructor(address identityRegistry_)
// Owner is deployer; owner = slash authority
```

### VaultfireTrustOracle
```solidity
constructor(address identityRegistry_)
// No owner; fully permissionless
```

### VaultfireForumRegistry
```solidity
// No constructor args; no owner; permissionless
```

### VaultfireBondInsurancePool
```solidity
constructor(address identityRegistry_)
// Owner is deployer; owner = claim resolver
```

### TrustDataBridge
```solidity
// No constructor args; deployer becomes owner + first relayer
// Post-deploy: addRelayer(), setRequiredRelayers(), configure chains
// DRAFT — not production-ready
```

### ConsentRegistry
```solidity
constructor(address ethicsModuleAddress)
// ethicsModuleAddress must be non-zero
```

---

*End of Vaultfire Codex Batch 6 — Governance, Privacy, and Miscellaneous Contracts*
