{
  "schema_version": "1.0.0",
  "bot_id": "2.4",
  "bot_name": "NonceShepherd",
  "slug": "nonceshepherd",
  "layer": "Execution",
  "layer_key": "exec",
  "bot_class": "Execution Utility",
  "authority": [
    "Reshape"
  ],
  "status": "live",
  "readiness": "General live",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Execution",
    "bot_class": "Execution Utility",
    "authority": "Reshape",
    "runs_before": "Order signing (EIP-712 buildOrderTypedData) and CLOB POST /order submission",
    "runs_after": "ExecutionPlan assembly (SmartRouter) and GasDecision (GasOracle)",
    "applies_to": "Every order that will be signed with the Polytraders wallet and submitted via CLOB V2",
    "default_mode": "general_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core"
  },
  "purpose": "NonceShepherd manages the on-chain nonce sequence for the Polytraders signing wallet on Polygon, ensuring that every V2 EIP-712 signed order carries a valid, non-colliding nonce. It detects nonce gaps (caused by dropped or reverted transactions), resequences pending orders when a gap is found, and enforces a ceiling on the number of pending signed-but-unposted orders. NonceShepherd also tracks CLOB ClobAuth credential TTLs and triggers re-authentication before they expire.",
  "why_it_matters": [
    {
      "failure": "Duplicate nonce on two simultaneous orders",
      "consequence": "CTFExchangeV2 rejects one of the two orders; fill rate drops and the strategy misses partial execution on fast-moving markets."
    },
    {
      "failure": "Nonce gap after a reverted transaction",
      "consequence": "All orders with nonces above the gap are stuck pending on-chain; they cannot fill until the gap is resolved, potentially leaving open risk positions unhedged."
    },
    {
      "failure": "Pending order count exceeds threshold",
      "consequence": "Signing queue backs up; later orders are assigned nonces that depend on earlier unfilled orders, creating a chain of blocked submissions."
    },
    {
      "failure": "Expired ClobAuth credential",
      "consequence": "CLOB POST /order returns 401; orders cannot be submitted until re-authentication completes, causing submission blackout."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "ClobAuth credential (L2 API key + secret + passphrase)",
      "source": "CLOB V2 auth endpoint",
      "required": true,
      "use": "Authenticate order submissions to the Polymarket CLOB V2. ClobAuth domain version is '1' (unchanged in V2)."
    }
  ],
  "internal_inputs": [
    {
      "input": "On-chain nonce for signing wallet from Polygon state",
      "source": "onchain RPC (eth_getTransactionCount)",
      "required": true,
      "use": "Determine the next valid nonce for the pending order; detect gaps by comparing on-chain nonce to internal sequence."
    },
    {
      "input": "Internal pending-order sequence table",
      "source": "internal Redis store",
      "required": true,
      "use": "Track which nonces have been assigned to signed-but-unposted orders to detect gaps and collisions."
    },
    {
      "input": "ExecutionPlan from SmartRouter",
      "source": "exec.smart_router",
      "required": true,
      "use": "Receive the order to be signed; assign nonce and inject builderCode (bytes32) before handing off to wallet adapter."
    }
  ],
  "raw_params": [
    "pending_orders_threshold \u00b7 int",
    "resequence_on_gap \u00b7 bool",
    "refuse_during_gap_s \u00b7 int",
    "l2_credential_ttl_h \u00b7 int"
  ],
  "parameters": [
    {
      "name": "pending_orders_threshold",
      "default": 10,
      "warning": 15,
      "hard": 20,
      "controls": "Maximum number of signed-but-unposted (pending) orders allowed in the signing queue at once. New order signing is blocked when this ceiling is reached.",
      "why_default_matters": "Allowing up to 10 pending orders balances throughput with the risk of a nonce gap cascading through a large queue.",
      "threshold_logic": [
        {
          "condition": "pending_count <= 10",
          "action": "APPROVE new signing request"
        },
        {
          "condition": "10 < pending_count <= 15",
          "action": "WARN \u2014 pending queue growing"
        },
        {
          "condition": "15 < pending_count <= 20",
          "action": "RESHAPE \u2014 slow down; hold new signing requests until queue drains below 10"
        },
        {
          "condition": "pending_count > 20",
          "action": "HARD_REJECT \u2014 queue at maximum; block signing until cleared"
        }
      ],
      "dev_check": "if pending_count >= params.hard: raise NonceQueueFullError('NONCE_SHEPHERD_QUEUE_FULL')",
      "user_facing": "Order placement is temporarily queued because several orders are still being processed."
    },
    {
      "name": "resequence_on_gap",
      "default": true,
      "warning": "\u2014",
      "hard": "\u2014",
      "controls": "When true, NonceShepherd automatically re-signs pending orders above a nonce gap with corrected nonces after gap resolution.",
      "why_default_matters": "Automatic resequencing minimises the window during which orders are stuck; manual resequencing would require operator intervention on every reverted transaction.",
      "threshold_logic": [
        {
          "condition": "resequence_on_gap=true AND gap detected",
          "action": "Re-sign affected orders with corrected nonces"
        },
        {
          "condition": "resequence_on_gap=false AND gap detected",
          "action": "Block signing until operator resolves gap manually"
        }
      ],
      "dev_check": "if nonce_gap and params.resequence_on_gap: resequence_pending_above(gap_nonce)",
      "user_facing": ""
    },
    {
      "name": "refuse_during_gap_s",
      "default": 30,
      "warning": 60,
      "hard": 120,
      "controls": "Seconds to refuse new signing requests while a nonce gap is being resolved. Prevents new orders from accumulating on top of an unresolved gap.",
      "why_default_matters": "A 30-second hold is long enough for most on-chain reorg resolutions without causing excessive submission latency.",
      "threshold_logic": [
        {
          "condition": "gap_age_s <= 30",
          "action": "Hold new signing \u2014 GAS_ORACLE_RPC_FAILURE or revert pending resolution"
        },
        {
          "condition": "30 < gap_age_s <= 60",
          "action": "WARN \u2014 gap taking longer than expected"
        },
        {
          "condition": "gap_age_s > 120",
          "action": "Escalate: emit NONCE_SHEPHERD_GAP_UNRESOLVED; pause signing until operator clears"
        }
      ],
      "dev_check": "if gap_age_s > params.hard: emit_alert('NONCE_SHEPHERD_GAP_UNRESOLVED')",
      "user_facing": "Order submission is paused briefly while a sequencing issue is resolved automatically."
    },
    {
      "name": "l2_credential_ttl_h",
      "default": 24,
      "warning": 2,
      "hard": 0,
      "controls": "Hours before a ClobAuth L2 credential expires at which NonceShepherd triggers a proactive re-authentication.",
      "why_default_matters": "Renewing credentials 24 hours before expiry ensures that a re-auth failure (e.g. CLOB downtime) still leaves a large buffer to retry before the credential actually expires.",
      "threshold_logic": [
        {
          "condition": "time_to_expiry > 24h",
          "action": "No action"
        },
        {
          "condition": "2h < time_to_expiry <= 24h",
          "action": "WARN \u2014 schedule re-auth"
        },
        {
          "condition": "time_to_expiry <= 2h",
          "action": "WARN \u2014 trigger immediate re-auth"
        },
        {
          "condition": "time_to_expiry <= 0",
          "action": "HARD_REJECT all order submissions until re-auth completes"
        }
      ],
      "dev_check": "if cred.expires_at - now_ms() < params.l2_credential_ttl_h * 3600 * 1000: schedule_reauth()",
      "user_facing": ""
    }
  ],
  "default_config": {
    "bot_id": "exec.nonce_shepherd",
    "version": "2.1.0",
    "mode": "general_live",
    "defaults": {
      "pending_orders_threshold": 10,
      "resequence_on_gap": true,
      "refuse_during_gap_s": 30,
      "l2_credential_ttl_h": 24
    },
    "locked": {
      "pending_orders_threshold": {
        "max": 20
      }
    }
  },
  "implementation_flow": [
    "Receive ExecutionPlan from SmartRouter including order parameters and builderCode (bytes32).",
    "Check current pending-order count; if >= pending_orders_threshold hard limit, emit HARD_REJECT with NONCE_SHEPHERD_QUEUE_FULL.",
    "Fetch current on-chain nonce for signing wallet via onchain RPC eth_getTransactionCount('latest').",
    "Compare on-chain nonce to internal sequence table; detect any gap (on-chain nonce > highest unconfirmed internal nonce).",
    "If gap detected and resequence_on_gap=true: mark gap, hold new signing for refuse_during_gap_s seconds, re-sign pending orders above gap with corrected nonces.",
    "If gap_age_s > refuse_during_gap_s hard limit: emit NONCE_SHEPHERD_GAP_UNRESOLVED alert and pause signing.",
    "Assign next available nonce to the incoming order; record assignment in internal sequence table.",
    "Check ClobAuth credential TTL; if time_to_expiry < l2_credential_ttl_h: trigger background re-auth.",
    "Inject nonce and builderCode (bytes32) into the order payload; forward to wallet adapter for EIP-712 signing.",
    "After CLOB POST /order confirms receipt, mark nonce as consumed in sequence table.",
    "On fill or cancellation confirmation, remove the nonce entry from the pending table."
  ],
  "decision_logic": {
    "approve": "Pending count below threshold, no active nonce gap, and ClobAuth credential is valid. Assign nonce and forward to wallet adapter.",
    "reshape_required": "Nonce gap detected: hold new signing, resequence pending orders above gap, assign corrected nonces after gap resolution. Pending count in warning band: slow submission rate.",
    "reject": "Pending count at hard ceiling (20), or nonce gap unresolved for > refuse_during_gap_s hard limit, or ClobAuth credential expired.",
    "warning_only": "Pending count in 10\u201315 warning band; or ClobAuth TTL approaching renewal threshold. Signing continues with WARN annotation."
  },
  "decision_output_schema": "NonceAssignment",
  "decision_output_example": {
    "shepherd_id": "exec.nonce_shepherd",
    "intent_id": "int_9a0b1c2d3e4f5a6b",
    "assigned_nonce": 1042,
    "builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "eip712_domain_version": "2",
    "clob_auth_domain_version": "1",
    "credential_ttl_remaining_h": 22.5,
    "pending_count_after": 7,
    "assigned_at_ms": 1746769000000
  },
  "developer_log": {
    "shepherd_id": "exec.nonce_shepherd",
    "intent_id": "int_9a0b1c2d3e4f5a6b",
    "assigned_nonce": 1042,
    "on_chain_nonce": 1040,
    "gap_detected": false,
    "pending_count_before": 6,
    "pending_count_after": 7,
    "credential_ttl_remaining_h": 22.5,
    "resequence_triggered": false,
    "builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "assigned_at_ms": 1746769000000
  },
  "user_explanations": [
    {
      "situation": "Order queued due to pending order backlog",
      "message": "Your order is in the queue while earlier orders are being confirmed. It will be submitted shortly."
    },
    {
      "situation": "Submission paused \u2014 nonce gap being resolved",
      "message": "Order submission is briefly paused while a sequencing issue is corrected automatically. No orders are lost."
    },
    {
      "situation": "Order blocked \u2014 credential renewal in progress",
      "message": "Order submission is paused for a moment while authentication credentials are refreshed."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "A nonce gap caused by a reverted transaction that NonceShepherd fails to detect (e.g. RPC returns stale nonce). All subsequent orders in the queue are stuck pending until the gap is detected and resolved.",
    "false_positive_risk": "Detecting a phantom nonce gap due to RPC latency returning an outdated nonce value, causing unnecessary resequencing and submission delay.",
    "false_negative_risk": "Missing a true nonce gap because the RPC node is lagging behind actual on-chain state, allowing duplicate-nonce submissions that CTFExchangeV2 will reject.",
    "safe_fallback": "If on-chain RPC is unreachable, pause all new signing requests and emit NONCE_SHEPHERD_RPC_FAILURE. If ClobAuth credential has expired, block submissions and surface NONCE_SHEPHERD_CREDENTIAL_EXPIRED.",
    "required_dependencies": [
      "Polygon onchain RPC (eth_getTransactionCount)",
      "Internal Redis nonce sequence table",
      "CLOB V2 auth endpoint (ClobAuth)",
      "Wallet adapter for EIP-712 signing"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Assign next sequential nonce when no gap exists",
        "setup": "on_chain_nonce=100, internal_sequence_head=102, pending=2",
        "expected": "NonceAssignment.assigned_nonce=103"
      },
      {
        "test": "Detect and hold on nonce gap",
        "setup": "on_chain_nonce=105, internal_sequence=[100..104, 106..109] (gap at 105)",
        "expected": "gap_detected=true; new signing held for refuse_during_gap_s=30s"
      },
      {
        "test": "Resequence pending orders above gap when resequence_on_gap=true",
        "setup": "gap at nonce 105; pending orders with nonces 106..109",
        "expected": "Orders 106..109 re-signed with corrected nonces 105..108"
      },
      {
        "test": "HARD_REJECT when pending count >= 20",
        "setup": "pending_count=20",
        "expected": "NONCE_SHEPHERD_QUEUE_FULL emitted; no nonce assigned"
      },
      {
        "test": "Trigger re-auth when credential TTL < l2_credential_ttl_h",
        "setup": "credential_expires_at = now + 20h, l2_credential_ttl_h=24",
        "expected": "Background re-auth scheduled; WARN annotation on NonceAssignment"
      },
      {
        "test": "builderCode (bytes32) injected into every NonceAssignment",
        "setup": "Standard ExecutionPlan input",
        "expected": "NonceAssignment.builder_code = 32-byte hex string"
      }
    ],
    "integration": [
      {
        "test": "End-to-end: ExecutionPlan \u2192 NonceAssignment \u2192 signed V2 order \u2192 CLOB POST /order",
        "expected": "Order accepted by CLOB; nonce marked consumed in sequence table"
      },
      {
        "test": "Nonce gap resolves and pending orders drain correctly",
        "expected": "After gap_resolved, resequenced orders submit in correct sequence with no duplicates"
      }
    ],
    "property": [
      {
        "property": "Each assigned nonce is unique across all pending orders",
        "required": "Always true \u2014 sequence table enforces uniqueness"
      },
      {
        "property": "builderCode is always present (non-null bytes32) on every NonceAssignment",
        "required": "Always true \u2014 builder_code_aware=true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Keep EIP-712 signing reliable under load.",
  "legacy_pm_signals": [
    "Pending signed-but-unposted orders count",
    "Nonce gaps detected during submit"
  ],
  "legacy_external_feeds": [
    "Safe wallet rotation events"
  ],
  "reporting_groups": [
    "execution"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "clob_auth",
    "onchain",
    "internal"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "2.1.0",
    "schema": "2",
    "released": "2026-04-28"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "v1",
      "to": "v2",
      "reason": "CLOB V2 cutover \u2014 EIP-712 domain version updated, order nonce semantics changed",
      "action_taken": "Updated EIP-712 Exchange domain version from '1' to '2'. Removed feeRateBps and legacy taker field from the order payload plumbing. Added builderCode (bytes32) injection into every NonceAssignment. ClobAuth domain version remains '1' (unchanged in V2). Collateral references updated from USDC.e to pUSD throughout shepherd logs."
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": false,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "NonceShepherd injects builderCode (bytes32) into every order payload before EIP-712 signing, making it a required node in the V2 builder attribution chain. ClobAuth domain version is '1' (Polymarket spec unchanged); Exchange EIP-712 domain is '2'. Fees are NOT in the signed order \u2014 operator-set by CTFExchangeV2 at match time."
  },
  "reference_implementation": {
    "summary": "Receives an ExecutionPlan, assigns the next available on-chain nonce, injects the builderCode (bytes32), checks ClobAuth credential TTL, and emits a NonceAssignment for the wallet adapter to sign. Detects and resequences nonce gaps automatically.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION assignNonce(execution_plan):\n  // --- 1. Pending queue gate ---\n  pending = FETCH internal.nonce_sequence_table.pending_count()\n  IF pending >= params.pending_orders_threshold_hard:\n    EMIT HARD_REJECT NONCE_SHEPHERD_QUEUE_FULL\n    RETURN\n\n  // --- 2. Fetch on-chain nonce ---\n  onchain_nonce = FETCH onchain_rpc.eth_getTransactionCount(wallet_address, 'latest')\n  IF onchain_nonce IS NULL:\n    EMIT HARD_REJECT NONCE_SHEPHERD_RPC_FAILURE\n    RETURN\n\n  // --- 3. Detect nonce gap ---\n  seq_head = internal.nonce_sequence_table.highest_pending()\n  IF onchain_nonce < seq_head - pending:  // gap exists\n    gap_nonce = onchain_nonce\n    EMIT WARN NONCE_SHEPHERD_GAP_DETECTED\n    IF params.resequence_on_gap:\n      resequenced = resequence_pending_above(gap_nonce)\n      // re-sign each affected order with corrected nonce\n      FOR order IN resequenced:\n        order.nonce = order.corrected_nonce\n        wallet.sign(buildOrderTypedData(order, { version: '2' }))\n    ELSE:\n      hold_new_signing(refuse_during_gap_s=params.refuse_during_gap_s)\n    gap_age_s = now_s() - gap_detected_at_s\n    IF gap_age_s > params.refuse_during_gap_s_hard:\n      EMIT ALERT NONCE_SHEPHERD_GAP_UNRESOLVED\n      RETURN\n\n  // --- 4. Assign nonce ---\n  next_nonce = seq_head + 1\n  internal.nonce_sequence_table.reserve(next_nonce, execution_plan.intent_id)\n\n  // --- 5. Inject builderCode (bytes32) ---\n  // V2 order field: builder(bytes32) \u2014 required for builder attribution\n  builder_code = config.builder_code  // 32-byte hex from config\n\n  // --- 6. ClobAuth credential TTL check ---\n  cred = FETCH internal.clob_auth_credentials()\n  ttl_h = (cred.expires_at - now_ms()) / 3_600_000\n  IF ttl_h <= 0:\n    EMIT HARD_REJECT NONCE_SHEPHERD_CREDENTIAL_EXPIRED\n    RETURN\n  IF ttl_h < params.l2_credential_ttl_h:\n    schedule_background_reauth()\n    EMIT WARN NONCE_SHEPHERD_CREDENTIAL_RENEWING\n\n  // --- 7. Emit NonceAssignment ---\n  EMIT NonceAssignment({\n    intent_id: execution_plan.intent_id,\n    assigned_nonce: next_nonce,\n    builder_code: builder_code,       // bytes32\n    eip712_domain_version: '2',\n    clob_auth_domain_version: '1',\n    credential_ttl_remaining_h: ttl_h,\n    pending_count_after: pending + 1,\n    assigned_at_ms: now_ms()\n  })\n",
    "sdk_calls": [
      "onchain_rpc.eth_getTransactionCount(wallet_address, 'latest')",
      "buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })",
      "clob_auth.POST('/auth/api-key', credentials)",
      "wallet.sign(typedData)"
    ],
    "complexity": "O(N) for resequencing where N = number of pending orders above the gap; O(1) in the normal (no-gap) path"
  },
  "wire_examples": {
    "input": {
      "label": "ExecutionPlan from SmartRouter awaiting nonce assignment",
      "source": "exec.smart_router",
      "payload": {
        "intent_id": "int_9a0b1c2d3e4f5a6b",
        "market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
        "side": "SELL",
        "outcome": "YES",
        "order_type": "GTC",
        "tick_aligned_price": 0.71,
        "size_usd": 300,
        "iceberg": false,
        "eip712_domain_version": "2",
        "generated_at_ms": 1746768990000
      }
    },
    "output": {
      "label": "NonceAssignment \u2014 nonce assigned, builderCode injected",
      "payload": {
        "shepherd_id": "exec.nonce_shepherd",
        "intent_id": "int_9a0b1c2d3e4f5a6b",
        "assigned_nonce": 1042,
        "builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
        "eip712_domain_version": "2",
        "clob_auth_domain_version": "1",
        "credential_ttl_remaining_h": 22.5,
        "pending_count_after": 7,
        "assigned_at_ms": 1746769000000
      }
    }
  },
  "reason_codes": [
    {
      "code": "NONCE_SHEPHERD_OK",
      "severity": "INFO",
      "meaning": "Nonce assigned successfully; no gap detected; queue healthy.",
      "action": "Emit NonceAssignment; proceed to wallet adapter.",
      "user_message": ""
    },
    {
      "code": "NONCE_SHEPHERD_QUEUE_FULL",
      "severity": "HARD_REJECT",
      "meaning": "Pending signed-but-unposted order count has reached the hard ceiling.",
      "action": "Block new signing requests until pending count drops below threshold.",
      "user_message": "Order placement is temporarily paused. Earlier orders are being confirmed."
    },
    {
      "code": "NONCE_SHEPHERD_GAP_DETECTED",
      "severity": "WARN",
      "meaning": "A gap in the nonce sequence was detected, indicating a reverted or dropped transaction.",
      "action": "Hold new signing for refuse_during_gap_s; trigger resequencing if resequence_on_gap=true.",
      "user_message": "Order submission is briefly paused while a sequencing issue is corrected."
    },
    {
      "code": "NONCE_SHEPHERD_GAP_UNRESOLVED",
      "severity": "HARD_REJECT",
      "meaning": "Nonce gap has not resolved within refuse_during_gap_s hard limit.",
      "action": "Pause all new signing; emit alert to on-call; await operator action.",
      "user_message": "Order submission is paused. Our team has been notified and is resolving the issue."
    },
    {
      "code": "NONCE_SHEPHERD_CREDENTIAL_RENEWING",
      "severity": "WARN",
      "meaning": "ClobAuth credential TTL is within l2_credential_ttl_h hours of expiry; background re-auth triggered.",
      "action": "Schedule background re-auth. Continue processing with current credential.",
      "user_message": ""
    },
    {
      "code": "NONCE_SHEPHERD_CREDENTIAL_EXPIRED",
      "severity": "HARD_REJECT",
      "meaning": "ClobAuth credential has expired; CLOB submissions will fail with 401.",
      "action": "Block all order submissions until re-auth completes.",
      "user_message": "Order submission is briefly paused while authentication is refreshed."
    },
    {
      "code": "NONCE_SHEPHERD_RPC_FAILURE",
      "severity": "HARD_REJECT",
      "meaning": "Cannot fetch on-chain nonce from Polygon RPC.",
      "action": "Block new signing. Emit alert. Resume once RPC recovers.",
      "user_message": "Order submission is paused due to a network connectivity issue."
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch active; no new nonces assigned.",
      "action": "Block all signing requests.",
      "user_message": "Trading is currently paused."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_exec_nonceshepherd_assignments_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total nonce assignment attempts by verdict and reason code."
      },
      {
        "name": "polytraders_exec_nonceshepherd_pending_count",
        "type": "gauge",
        "unit": "count",
        "labels": [],
        "meaning": "Current number of signed-but-unposted orders in the pending queue."
      },
      {
        "name": "polytraders_exec_nonceshepherd_gap_events_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "resolved"
        ],
        "meaning": "Total nonce gap events detected; label resolved=true/false."
      },
      {
        "name": "polytraders_exec_nonceshepherd_credential_ttl_hours",
        "type": "gauge",
        "unit": "hours",
        "labels": [],
        "meaning": "Remaining TTL (hours) of the current ClobAuth credential; alert when < 2h."
      },
      {
        "name": "polytraders_exec_nonceshepherd_resequence_count",
        "type": "counter",
        "unit": "count",
        "labels": [],
        "meaning": "Total orders that have been re-signed due to nonce gap resequencing."
      },
      {
        "name": "polytraders_exec_nonceshepherd_assign_latency_ms",
        "type": "histogram",
        "unit": "ms",
        "labels": [],
        "meaning": "Wall-clock time from ExecutionPlan receipt to NonceAssignment emit."
      }
    ],
    "alerts": [
      {
        "name": "NonceShepherdGapUnresolved",
        "condition": "rate(polytraders_exec_nonceshepherd_gap_events_total{resolved='false'}[5m]) > 0",
        "severity": "page",
        "runbook": "#runbook-nonceshepherd-gap-unresolved"
      },
      {
        "name": "NonceShepherdQueueFull",
        "condition": "polytraders_exec_nonceshepherd_pending_count >= 18",
        "severity": "warn",
        "runbook": "#runbook-nonceshepherd-queue-full"
      },
      {
        "name": "NonceShepherdCredentialExpiringSoon",
        "condition": "polytraders_exec_nonceshepherd_credential_ttl_hours < 2",
        "severity": "page",
        "runbook": "#runbook-nonceshepherd-credential-expiry"
      },
      {
        "name": "NonceShepherdRpcDown",
        "condition": "rate(polytraders_exec_nonceshepherd_assignments_total{reason_code='NONCE_SHEPHERD_RPC_FAILURE'}[5m]) > 0",
        "severity": "page",
        "runbook": "#runbook-nonceshepherd-rpc-failure"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Execution / NonceShepherd",
      "Grafana \u2014 Order signing / nonce gap history and pending queue depth"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "redis",
    "shape": "Nonce sequence table: hash keyed by nonce \u2192 { intent_id, assigned_at_ms, status: pending|consumed|gap }. ClobAuth credential: serialised key/secret/passphrase + expires_at_ms.",
    "ttl": "Nonce entries expire 3600s after assignment if never confirmed; credential entries expire at credential expiry + 60s buffer",
    "recovery": "On restart, Redis nonce table is replayed to reconstruct pending state. Any nonce without on-chain confirmation is treated as potentially gapped and re-verified via RPC.",
    "size_estimate": "~200 bytes per pending nonce entry; max 20 entries = ~4 KB; credential ~1 KB"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 20,
    "idempotency_key": "intent_id",
    "timeout_ms": 300,
    "backpressure": "Block new requests when pending_count >= hard limit; internal queue for retry after gap resolution",
    "locking": "Redis SETNX per nonce to prevent double-assignment under concurrent requests"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "exec.smart_router",
        "why": "Receives ExecutionPlan containing order parameters and builderCode for nonce assignment.",
        "contract": "ExecutionPlan must include intent_id and eip712_domain_version='2'."
      },
      {
        "bot_id": "exec.gas_oracle",
        "why": "GasOracle DEFER signals pause nonce reservation for queued ops to prevent nonce starvation.",
        "contract": "NonceShepherd holds reservations while GasOracle DEFER is active."
      },
      {
        "bot_id": "risk.kill_switch",
        "why": "KillSwitch active blocks all new nonce assignments.",
        "contract": "No NonceAssignment emitted when KillSwitch is active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "gov.builder_attribution",
        "what": "builderCode (bytes32) is injected into every NonceAssignment and carried into the signed order for attribution logging.",
        "contract": "builder_code field non-null on every NonceAssignment."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polygon RPC (nonce feed)",
        "endpoint": "https://polygon-rpc.com (or internal RPC node)",
        "sla": "best-effort / internal node target 99.9%",
        "fallback": "Block signing on RPC failure; emit NONCE_SHEPHERD_RPC_FAILURE."
      },
      {
        "service": "Polymarket CLOB V2 auth",
        "endpoint": "https://clob.polymarket.com/auth/api-key",
        "sla": "99.9% (Polymarket-published)",
        "fallback": "Continue with current credential until it expires; surface NONCE_SHEPHERD_CREDENTIAL_EXPIRED on expiry."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Injecting a crafted nonce into the sequence table to displace a legitimate pending order",
      "Replaying a stale NonceAssignment with an already-consumed nonce to force a CTFExchangeV2 rejection and DOS the submission pipeline",
      "Manipulating ClobAuth credential TTL to prevent timely renewal, causing a submission blackout"
    ],
    "mitigations": [
      "Redis nonce table write access restricted to NonceShepherd service account via ACL",
      "NonceAssignment records are HMAC-signed; consumers reject tampered records",
      "Nonce deduplication: each nonce can only be in state pending|consumed \u2014 duplicate reservation rejected",
      "ClobAuth re-auth triggered 24h before expiry providing a large retry buffer"
    ],
    "contract_calls": []
  },
  "failure_injection": [
    {
      "scenario": "NONCE_GAP_SIMULATED",
      "how_to_inject": "Remove entry for nonce N from Redis sequence table while orders N+1..N+3 are pending",
      "expected_behaviour": "NonceShepherd detects gap at N; holds new signing; triggers resequencing of N+1..N+3 with corrected nonces",
      "recovery": "After resequencing completes, signing resumes; gap_events_total{resolved='true'} increments"
    },
    {
      "scenario": "QUEUE_FULL",
      "how_to_inject": "Inject 20 pending nonce entries into Redis without consuming any",
      "expected_behaviour": "NonceShepherd rejects new assignments with NONCE_SHEPHERD_QUEUE_FULL",
      "recovery": "Clears as pending orders confirm on-chain and are marked consumed"
    },
    {
      "scenario": "RPC_FAILURE",
      "how_to_inject": "Block TCP to Polygon RPC endpoint",
      "expected_behaviour": "NonceShepherd emits NONCE_SHEPHERD_RPC_FAILURE; blocks all new signing; alert fires",
      "recovery": "Automatically resumes signing on RPC reconnect"
    },
    {
      "scenario": "CREDENTIAL_EXPIRED",
      "how_to_inject": "Set ClobAuth credential expires_at to now() - 1s in Redis",
      "expected_behaviour": "NonceShepherd blocks all submissions with NONCE_SHEPHERD_CREDENTIAL_EXPIRED; re-auth triggered",
      "recovery": "Re-auth completes; new credential stored; signing resumes"
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Activate global KillSwitch",
      "expected_behaviour": "All NonceAssignment requests blocked; no nonces assigned",
      "recovery": "Resumes on manual KillSwitch reset"
    }
  ],
  "runbook": {
    "summary": "NonceShepherd incidents are nonce gap events (usually caused by reverted transactions), queue full conditions during high-throughput periods, or ClobAuth credential expiry. All three cause submission blackouts of varying severity.",
    "oncall_actions": [
      {
        "alert": "NonceShepherdGapUnresolved",
        "first_step": "Check Grafana for gap_nonce value and pending order count. Verify on-chain nonce via eth_getTransactionCount.",
        "diagnosis": "A transaction was reverted or dropped; gap_nonce is the nonce at which the sequence breaks.",
        "mitigation": "Run: polytraders bot resequence exec.nonce_shepherd --from-nonce <gap_nonce>",
        "escalation": "Exec pod lead immediately \u2014 unresolved gap halts all order signing"
      },
      {
        "alert": "NonceShepherdQueueFull",
        "first_step": "Check CLOB fill confirmation latency. If CLOB is slow to confirm, reduce strategy throughput temporarily.",
        "diagnosis": "Pending orders are not being consumed fast enough; queue has reached the hard ceiling.",
        "mitigation": "Reduce strategy order rate. If CLOB is slow, check Polymarket status page.",
        "escalation": "Exec pod lead if queue does not drain within 5 minutes"
      },
      {
        "alert": "NonceShepherdCredentialExpiringSoon",
        "first_step": "Trigger manual re-auth via: polytraders bot reauth exec.nonce_shepherd",
        "diagnosis": "ClobAuth credential approaching expiry with < 2h remaining.",
        "mitigation": "Run re-auth immediately. If re-auth fails, check CLOB auth endpoint availability.",
        "escalation": "Exec pod lead if re-auth fails within 30 minutes of expiry"
      },
      {
        "alert": "NonceShepherdRpcDown",
        "first_step": "Verify Polygon RPC endpoint. Switch to fallback RPC node if primary is down.",
        "diagnosis": "RPC unreachable; cannot determine on-chain nonce; all signing blocked.",
        "mitigation": "Switch to fallback RPC node. Restore primary if internal node.",
        "escalation": "Infra on-call if RPC unreachable for > 5 minutes"
      }
    ],
    "manual_overrides": [
      {
        "command": "polytraders bot resequence exec.nonce_shepherd --from-nonce <N>",
        "effect": "Manually triggers nonce resequencing from nonce N. Use when automatic resequencing fails."
      },
      {
        "command": "polytraders bot reauth exec.nonce_shepherd",
        "effect": "Forces immediate ClobAuth credential renewal. Use when automatic re-auth has failed or credential is near expiry."
      }
    ],
    "healthcheck": "GET /internal/health/nonceshepherd \u2192 200 if no active gap, pending_count < 15, ClobAuth credential valid > 2h, RPC reachable. RED if active nonce gap > refuse_during_gap_s, pending_count >= 20, credential expired, or RPC unreachable."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All 6 acceptance_tests.unit cases pass",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "builderCode (bytes32) present on every NonceAssignment in integration test",
        "how_measured": "Integration test: inspect NonceAssignment.builder_code field",
        "threshold": "Non-null bytes32 on 100% of assignments"
      }
    ],
    "to_limited_live": [
      {
        "gate": "Nonce gap simulation: gap detected and resequenced correctly",
        "how_measured": "Failure injection: NONCE_GAP_SIMULATED scenario",
        "threshold": "Pass \u2014 orders resequenced without duplicate nonces"
      },
      {
        "gate": "p99 assign latency < 300ms over 24h",
        "how_measured": "polytraders_exec_nonceshepherd_assign_latency_ms histogram",
        "threshold": "p99 < 300ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: ExecutionPlan \u2192 NonceAssignment \u2192 signed V2 order \u2192 fill on Polygon testnet",
        "how_measured": "E2E test on Polygon Amoy testnet",
        "threshold": "Pass with correct builderCode on signed order"
      },
      {
        "gate": "7-day production shadow: zero duplicate nonces detected",
        "how_measured": "Audit log \u2014 nonce sequence table review",
        "threshold": "Zero duplicates"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "ExecutionReport"
    ],
    "topics": [
      "polytraders.reports.exec"
    ],
    "cadence": "every-event",
    "retention_class": "7y",
    "sampling_rule": "emit-every",
    "bus_failure_action": "wal-then-retry",
    "user_visible": "summary-only",
    "consumes_kinds": []
  },
  "capital_impact": "Direct",
  "mode_support": [
    "quarantine"
  ],
  "v3_status": {
    "phase": 5,
    "phase_name": "Execution rails",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}