{
  "schema_version": "1.0.0",
  "bot_id": "6.19",
  "bot_name": "ConfigDriftDetector",
  "slug": "config_drift_detector",
  "layer": "Governance",
  "layer_key": "gov",
  "bot_class": "Governance Service",
  "authority": [
    "Observe"
  ],
  "status": "planned",
  "readiness": "Spec ready",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Governance",
    "bot_class": "Governance",
    "authority": "Observe",
    "runs_before": "\u2014",
    "runs_after": "\u2014",
    "applies_to": "Continuous",
    "default_mode": "shadow",
    "user_visible": "Yes",
    "developer_owner": "Governance pod"
  },
  "purpose": "Compares the running BotConfig of every live bot against the latest committed config in the config repo. Any drift (running != committed) is surfaced as a ConfigDriftReport naming the bot, the field, and the drift amount. Operators are forced to either commit the change or revert it.",
  "why_it_matters": [
    {
      "failure": "Untracked tuning",
      "consequence": "An on-call who tweaks a threshold via the Admin UI without committing the change loses an audit trail; the next incident review cannot reconstruct the system state."
    },
    {
      "failure": "Drift between staging and prod",
      "consequence": "Without an explicit comparison, prod can silently run an ancient config while staging is updated."
    },
    {
      "failure": "Compliance evidence",
      "consequence": "Auditors require evidence that the running configuration matches a reviewed and signed-off version."
    }
  ],
  "polymarket_inputs": [],
  "internal_inputs": [
    {
      "input": "Running BotConfig per bot",
      "source": "Bot runtime",
      "required": true,
      "use": "Effective config in process memory, including any live-edited fields."
    },
    {
      "input": "Committed BotConfig per bot",
      "source": "Config repo (Git)",
      "required": true,
      "use": "Source of truth signed-off configuration."
    }
  ],
  "raw_params": [
    "check_interval_minutes \u00b7 1\u20131440",
    "tolerance_for_numeric_drift \u00b7 0\u201310"
  ],
  "parameters": [
    {
      "name": "check_interval_minutes",
      "default": 15,
      "warning": "30",
      "hard": "60",
      "controls": "How often the drift comparison runs.",
      "why_default_matters": "Quarter-hourly is frequent enough to catch live edits before they outlive the on-call shift.",
      "threshold_logic": [
        {
          "condition": "15",
          "action": "Default"
        }
      ],
      "dev_check": "schedule.every(p.check_interval_minutes).do(check);",
      "user_facing": "(Internal.)"
    },
    {
      "name": "tolerance_for_numeric_drift",
      "default": 0,
      "warning": "0",
      "hard": "0.001",
      "controls": "Tolerance for numeric fields before a drift is flagged.",
      "why_default_matters": "Zero \u2014 there is no acceptable silent drift in production. Use `human_override` to record an intentional change.",
      "threshold_logic": [
        {
          "condition": "0",
          "action": "Default \u2014 strict"
        }
      ],
      "dev_check": "if (abs(running - committed) > p.tolerance_for_numeric_drift) flag(field);",
      "user_facing": "(Internal.)"
    }
  ],
  "default_config": {
    "check_interval_minutes": 15,
    "tolerance_for_numeric_drift": 0
  },
  "flow": "On schedule \u2192 fetch each running bot's BotConfig \u2192 fetch the committed BotConfig from the config repo \u2192 diff field-by-field \u2192 emit ConfigDriftReport per drifting bot listing { field, running, committed, since_ts_ms }.",
  "decision_logic": {
    "approve": "Strict equality on enums and strings. Numeric tolerance applied via `tolerance_for_numeric_drift`. Drift latched until either a commit or revert resolves it.",
    "reshape_required": "This bot does not reshape orders.",
    "reject": "No reject path defined for this bot \u2014 it is observe-only.",
    "warning_only": "No warn-only path defined."
  },
  "decision_output_example": {
    "kind": "ConfigDriftReport",
    "bot_slug": "risk.killswitch",
    "drifts": [
      {
        "field": "intraday_drawdown_pct",
        "running": 10,
        "committed": 12,
        "since_ts_ms": 1715260000000
      }
    ]
  },
  "developer_log": "Per check: bot_slug, drifts_count, fields_drifted.",
  "user_explanations": [
    {
      "situation": "When this bot acts",
      "message": "The running configuration of one of the system's safeties differs from the version on file. Operators must reconcile."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Comparing against the wrong committed revision (e.g. wrong branch).",
    "false_positive_risk": "Differences in field ordering or default-equivalence falsely flagged; mitigation: canonicalise both sides through the JSON Schema before diffing.",
    "false_negative_risk": "Bot's running config object is missing a field the committed version added; mitigation: schema-validate both sides and treat missing-vs-present as drift.",
    "safe_fallback": "If the committed config cannot be fetched, emit ConfigDriftReport with status=UNKNOWN \u2014 never silently report 'no drift'."
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Identical configs report no drift.",
        "setup": "Synthetic fixture per template.",
        "expected": "Behaviour matches the rule described in the test name."
      },
      {
        "test": "One numeric field changed by 1 reports the drift exactly.",
        "setup": "Synthetic fixture per template.",
        "expected": "Behaviour matches the rule described in the test name."
      }
    ],
    "integration": [
      {
        "test": "Bump a running config via Admin UI without committing; the next check emits a drift report within one interval.",
        "expected": "End-to-end behaviour matches the spec without manual intervention."
      }
    ],
    "property": [
      {
        "property": "For any (running, committed), the drift list contains exactly the fields whose canonicalised values differ.",
        "required": "Always true across all generated inputs."
      }
    ]
  },
  "reference_implementation": {
    "language": "pseudocode",
    "pseudocode": "for bot in registry.live_bots():\n  running = bot.runtime_config()\n  committed = repo.config(bot.slug, branch='main')\n  diffs = canonical_diff(running, committed, p.tolerance_for_numeric_drift)\n  if diffs: emit('ConfigDriftReport', bot.slug, diffs)"
  },
  "wire_examples": {
    "input": {
      "bot_slug": "risk.killswitch",
      "running": {
        "intraday_drawdown_pct": 10
      },
      "committed": {
        "intraday_drawdown_pct": 12
      }
    },
    "output": {
      "kind": "ConfigDriftReport",
      "bot_slug": "risk.killswitch",
      "drifts": [
        {
          "field": "intraday_drawdown_pct",
          "running": 10,
          "committed": 12
        }
      ]
    }
  },
  "reason_codes": [
    {
      "code": "GOV_CONFIG_DRIFT_DETECTED",
      "severity": "P3",
      "meaning": "Gov Config Drift Detected",
      "action": "See decision output and developer log for context.",
      "user_message": "The running configuration of one of the system's safeties differs from the version on file. Operators must reconcile."
    },
    {
      "code": "GOV_CONFIG_DRIFT_RESOLVED",
      "severity": "P3",
      "meaning": "Gov Config Drift Resolved",
      "action": "See decision output and developer log for context.",
      "user_message": "The running configuration of one of the system's safeties differs from the version on file. Operators must reconcile."
    },
    {
      "code": "GOV_CONFIG_DRIFT_UNKNOWN",
      "severity": "P3",
      "meaning": "Gov Config Drift Unknown",
      "action": "See decision output and developer log for context.",
      "user_message": "The running configuration of one of the system's safeties differs from the version on file. Operators must reconcile."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "drift_reports_total",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Drift reports total."
      },
      {
        "name": "bots_in_drift",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Bots in drift."
      },
      {
        "name": "drift_resolution_minutes_histogram",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Drift resolution minutes histogram."
      }
    ],
    "alerts": [],
    "dashboards": [
      "6.19 overview dashboard"
    ]
  },
  "state": {
    "summary": "Last drift report per bot. Persisted to KV.",
    "stores": [
      {
        "name": "config_drift_detector_state",
        "kind": "in-memory + fast KV mirror",
        "key": "bot_id",
        "value": "Last drift report per bot. Persisted to KV.",
        "ttl": "24h",
        "durability": "crash-safe via KV mirror"
      }
    ],
    "recovery": "Cold-start hydrates from fast KV; missing keys default to safe fallback.",
    "on_restart": "All in-flight decisions are re-evaluated; no bot decision is trusted across restart without re-emit."
  },
  "concurrency": {
    "execution_model": "Single scheduled checker; no per-bot fan-out.",
    "max_in_flight": 32,
    "idempotency_key": "order_intent_id",
    "replay_safe": true,
    "deduplication": "By idempotency_key within a 60s window.",
    "ordering_guarantees": "Per-market_id FIFO; cross-market unordered.",
    "timeout_ms": 250,
    "backpressure": "Bounded queue; oldest-dropped with metric increment when full.",
    "locking": "Per-market_id mutex; no global locks."
  },
  "dependencies": {
    "depends_on": [],
    "emits_to": []
  },
  "graph": {
    "requires": [],
    "required_before": [],
    "consumes": [
      "BotConfigRunning",
      "BotConfigCommitted"
    ],
    "emits": [
      "OperationsReport(kind=ConfigDriftReport)"
    ],
    "blocks": false
  },
  "mode_support": [
    "off",
    "shadow",
    "advisory",
    "enforced"
  ],
  "latency_budget_ms": 5000,
  "data_freshness": {
    "max_market_data_age_ms": 900000,
    "max_orderbook_age_ms": 900000,
    "max_external_feed_age_ms": 900000,
    "on_stale_data": "Emit status=UNKNOWN."
  },
  "ownership": {
    "owner": "Governance pod",
    "on_call": "gov-oncall",
    "channel": "#polytraders-gov",
    "escalation": "Head of Governance",
    "severity_class": "P2"
  },
  "human_override": {
    "allowed": true,
    "who": "Governance on-call",
    "log_event": "GOV_CONFIG_DRIFT_ACK",
    "time_bound": "Until next check",
    "scope": "Single bot_slug",
    "second_approval": true
  },
  "security_surfaces": {
    "summary": "Read-only access to config repo. Read-only RPC into bot runtime.",
    "signing": "None \u2014 bot does not sign or submit.",
    "secrets": [],
    "contract_calls": [],
    "abuse_vectors": [],
    "mitigations": [
      "Rate-limit per source",
      "Audit-log every override",
      "Require role-based authz on admin paths"
    ]
  },
  "polymarket_v2_compat": {
    "clob_version": "V2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": true,
    "multichain_ready": true,
    "sdk_used": "Polymarket CLOB V2 SDK",
    "settlement_contract": "CTFExchangeV2",
    "notes": "Operates on V2 BotConfig schema only."
  },
  "version": {
    "current": "0.1.0",
    "contract_version": "1.0.0",
    "last_breaking_change": "none",
    "deprecation_window_days": 30
  },
  "migration_history": [],
  "runbook": {
    "summary": "If multiple bots drift simultaneously: confirm the config repo branch the checker is reading from is the production branch.",
    "oncall_actions": [
      {
        "alert": "6.19_anomaly",
        "first_step": "Open the bot's reporting page and confirm the alert is real (not a metric hiccup).",
        "diagnosis": "Inspect developer log entries for the affected market_id over the last 30 minutes.",
        "mitigation": "Force-clear via Admin UI if the rule is clearly stale; otherwise leave engaged and notify owner.",
        "escalation": "Governance pod"
      }
    ],
    "manual_overrides": [
      {
        "command": "polytraders bot pause 6.19",
        "effect": "Disables the bot's enforcement layer; downstream consumers fall back to safe defaults."
      }
    ],
    "healthcheck": "GET /healthz/config_drift_detector \u2192 200 if last successful evaluation < 60s ago."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Stub",
        "how_measured": "against synthetic drifts.",
        "threshold": "Documented threshold met for the full window."
      }
    ],
    "to_limited_live": [
      {
        "gate": "Shadow",
        "how_measured": "14 days; reports compared by Governance on-call.",
        "threshold": "Documented threshold met for the full window."
      },
      {
        "gate": "Advisory",
        "how_measured": "7 days.",
        "threshold": "Documented threshold met for the full window."
      }
    ],
    "to_general_live": [
      {
        "gate": "Enforced",
        "how_measured": "drift reports break the daily ops digest.",
        "threshold": "Documented threshold met for the full window."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "Block the config repo and assert UNKNOWN is emitted",
      "how_to_inject": "Block the config repo and assert UNKNOWN is emitted.",
      "expected_behavior": "Bot detects within its latency budget and emits the corresponding reason code.",
      "recovery": "Remove the injected fault; bot returns to healthy state within one debounce window."
    },
    {
      "scenario": "Drift one field and assert the report contains exactly that field",
      "how_to_inject": "Drift one field and assert the report contains exactly that field.",
      "expected_behavior": "Bot detects within its latency budget and emits the corresponding reason code.",
      "recovery": "Remove the injected fault; bot returns to healthy state within one debounce window."
    }
  ],
  "capital_impact": "Direct",
  "v3_status": {
    "phase": 7,
    "phase_name": "Governance & replay",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}