{
  "schema_version": "1.0.0",
  "bot_id": "6.8",
  "bot_name": "StrategyRegistry",
  "slug": "strategyregistry",
  "layer": "Governance",
  "layer_key": "gov",
  "bot_class": "Governance Service",
  "authority": [
    "Explain"
  ],
  "status": "planned",
  "readiness": "Spec started",
  "flagship": true,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Governance",
    "bot_class": "Governance Service",
    "authority": "Explain",
    "runs_before": "Backtester, ExperimentTracker, ExecRouter \u2014 any bot that needs to know a strategy's current stage",
    "runs_after": "Governance pod promotion review",
    "applies_to": "Every registered strategy bot at every lifecycle stage",
    "default_mode": "shadow_only",
    "user_visible": "no",
    "developer_owner": "Polytraders core"
  },
  "purpose": "StrategyRegistry is the single source of truth for which strategy is at which lifecycle stage and under what authority. It stores stage metadata, promotion history, linked artefacts, and live-cap policy.",
  "why_it_matters": [
    {
      "failure": "No centralised registry",
      "consequence": "Multiple bots may execute strategies at conflicting stages, making audits impossible."
    },
    {
      "failure": "Promotion without recorded approval",
      "consequence": "Compliance audit finds no evidence trail; promotion may need to be reversed."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "None \u2014 StrategyRegistry is a pure internal governance store",
      "source": "internal",
      "required": false,
      "use": "N/A"
    }
  ],
  "internal_inputs": [
    {
      "input": "Promotion requests from governance pod",
      "source": "internal",
      "required": true,
      "use": "Record stage transitions and approver identities."
    },
    {
      "input": "Backtest report artefact reference",
      "source": "gov.backtester",
      "required": true,
      "use": "Link the artefact to the strategy record before shadow promotion."
    }
  ],
  "raw_params": [
    "require_two_person_promotion \u00b7 list",
    "min_shadow_days \u00b7 int",
    "min_backtest_days \u00b7 int",
    "auto_demote_on_drift \u00b7 bool"
  ],
  "parameters": [
    {
      "name": "require_two_person_promotion",
      "default": [
        "to_limited_live",
        "to_general_live"
      ],
      "warning": null,
      "hard": null,
      "controls": "List of stage transitions that require two distinct approvers.",
      "why_default_matters": "Two-person rule prevents a single engineer from promoting to live unilaterally.",
      "threshold_logic": [
        {
          "condition": "transition in require_two_person_promotion",
          "action": "Require second approver before writing stage change"
        }
      ],
      "dev_check": "if stage in p.require_two_person_promotion and approvers.count < 2: raise ConfigError",
      "user_facing": "Critical promotions require approval from two team members."
    },
    {
      "name": "min_shadow_days",
      "default": 3,
      "warning": 7,
      "hard": 30,
      "controls": "Minimum days a strategy must run in shadow before limited-live promotion.",
      "why_default_matters": "3 days provides enough data for initial statistical comparison.",
      "threshold_logic": [
        {
          "condition": "shadow_days < min_shadow_days",
          "action": "Block promotion; emit PROMOTION_GATE_FAILED"
        }
      ],
      "dev_check": "if shadow_days < p.min_shadow_days: raise PromotionError('PROMOTION_GATE_FAILED')",
      "user_facing": "Strategies run in shadow mode for at least 3 days before going live."
    }
  ],
  "default_config": {
    "bot_id": "gov.strategyregistry",
    "version": "0.1.0",
    "mode": "shadow_only",
    "defaults": {
      "require_two_person_promotion": [
        "to_limited_live",
        "to_general_live"
      ],
      "min_shadow_days": 3,
      "min_backtest_days": 7,
      "auto_demote_on_drift": true
    }
  },
  "implementation_flow": [
    "On startup, load the strategy registry table from Postgres; index by slug.",
    "When a promotion request arrives, validate artefacts and approver count against promotion gates.",
    "Write stage change to registry with timestamp, approver IDs, and linked artefact references.",
    "Emit OperationsReport(event_type=STRATEGY_PROMOTED) to polytraders.reports.operations.",
    "On drift signal from ExperimentTracker, auto-demote to previous stage if auto_demote_on_drift=true.",
    "Provide read API for other bots to query current strategy stage and live-cap."
  ],
  "decision_logic": {
    "approve": "Not applicable \u2014 StrategyRegistry records decisions made by governance pod humans.",
    "reshape_required": "Not applicable.",
    "reject": "Rejects promotion if gates are not met; emits PROMOTION_GATE_FAILED.",
    "warning_only": "Emits STRATEGY_DRIFT_DETECTED if auto-demote criteria met but auto_demote is disabled."
  },
  "decision_output_schema": "OperationsReport",
  "decision_output_example": {
    "report_id": "ops_strategyregistry_01HX9Z",
    "bot_id": "gov.strategyregistry",
    "event_type": "STRATEGY_PROMOTED",
    "slug": "sports-model",
    "from_stage": "shadow",
    "to_stage": "limited_live",
    "approvers": [
      "alice@polytraders",
      "bob@polytraders"
    ],
    "artefacts": {
      "backtest_report": "bt_01HX8Z",
      "shadow_report": "sh_01HX9Y"
    },
    "report_kind": "OperationsReport",
    "topic": "polytraders.reports.operations",
    "retained_until": "2027-05-09"
  },
  "developer_log": {
    "bot_id": "gov.strategyregistry",
    "event_type": "REGISTRY_QUERY",
    "slug": "sports-model",
    "stage": "limited_live",
    "queried_at_ms": 1746792060000
  },
  "user_explanations": [
    {
      "situation": "Strategy promoted to limited live",
      "message": "The strategy passed its shadow-mode review and has been approved for limited live operation."
    },
    {
      "situation": "Promotion blocked",
      "message": "The strategy has not met all required checks for this stage."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Postgres registry store is unavailable; bots cannot query stage metadata.",
    "false_positive_risk": "Auto-demotion triggered by a transient drift spike rather than a genuine regression.",
    "false_negative_risk": "Promotion gate artefacts are present but stale; gate passes on outdated data.",
    "safe_fallback": "If registry is unavailable, bots default to the last known stage cached in memory.",
    "required_dependencies": [
      "Postgres registry store",
      "gov.backtester artefact store",
      "gov.experimenttracker drift signal"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Promotion blocked when min_shadow_days not met",
        "setup": "shadow_days=1, min_shadow_days=3",
        "expected": "PROMOTION_GATE_FAILED"
      },
      {
        "test": "Two-person rule enforced for to_general_live",
        "setup": "approvers=['alice']",
        "expected": "PROMOTION_GATE_FAILED"
      }
    ],
    "integration": [
      {
        "test": "Full promotion lifecycle: research \u2192 backtest \u2192 shadow \u2192 limited_live emits correct OperationsReport chain",
        "expected": "4 OperationsReport records, each with correct stage transition"
      }
    ],
    "property": [
      {
        "property": "Every stage transition is recorded with approver IDs and artefact references",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Single source of truth for which strategy is at which lifecycle stage and on what authority.",
  "legacy_pm_signals": [
    "Per-strategy stage: Research, Backtest, Shadow, Limited Live, General Live, Watchlist, Deprecated",
    "Promotion tickets and approver list per stage transition",
    "Linked artefacts: backtest report, shadow report, drift status",
    "Live-cap policy and current consumption"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "governance_audit"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "internal"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "0.1.0",
    "schema": "2",
    "released": null,
    "planned_release": "Q3-2026"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "n/a",
      "to": "v2-spec",
      "reason": "Spec drafted post-CLOB-V2 cutover; bot not yet implemented",
      "action_taken": "Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain)"
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": false,
    "negrisk_aware": false,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "StrategyRegistry is a pure internal governance store; no direct CLOB calls. Spec designed against V2 pUSD denomination."
  },
  "reference_implementation": {
    "pseudocode": "// ---- STARTUP ----\nFUNCTION init():\n  registry = postgres.loadAll('strategy_registry')\n  indexBySlug(registry)\n\n// ---- PROMOTION REQUEST ----\nFUNCTION handlePromotionRequest(req):\n  entry = registry.get(req.slug)\n  IF entry IS NULL: RAISE 'STRATEGY_NOT_FOUND'\n  IF NOT artefactsPresent(entry, req.to_stage): RAISE 'PROMOTION_GATE_FAILED'\n  IF req.to_stage IN config.require_two_person_promotion:\n    IF len(req.approvers) < 2: RAISE 'PROMOTION_GATE_FAILED'\n  IF entry.stage == 'shadow' AND entry.shadow_days < config.min_shadow_days:\n    RAISE 'PROMOTION_GATE_FAILED'\n  entry.stage = req.to_stage\n  entry.updated_at = now()\n  postgres.upsert('strategy_registry', entry)\n  EMIT OperationsReport(event_type='STRATEGY_PROMOTED', slug=req.slug,\n    from_stage=entry.prev_stage, to_stage=req.to_stage,\n    approvers=req.approvers)\n\n// ---- DRIFT AUTO-DEMOTE ----\nFUNCTION onDriftSignal(slug):\n  IF config.auto_demote_on_drift:\n    entry = registry.get(slug)\n    entry.stage = entry.prev_stage\n    postgres.upsert('strategy_registry', entry)\n    EMIT OperationsReport(event_type='STRATEGY_DEMOTED', slug=slug)\n  ELSE:\n    EMIT OperationsReport(event_type='STRATEGY_DRIFT_DETECTED', slug=slug)",
    "sdk_calls": [
      "postgres.loadAll('strategy_registry')",
      "postgres.upsert('strategy_registry', entry)",
      "alerting.emit('PROMOTION_GATE_FAILED', metadata)"
    ],
    "complexity": "O(1) per promotion request; O(N) on startup where N = registered strategies"
  },
  "wire_examples": {
    "input": {
      "label": "Promotion request",
      "source": "governance_pod",
      "payload": {
        "slug": "sports-model",
        "from_stage": "shadow",
        "to_stage": "limited_live",
        "approvers": [
          "alice@polytraders",
          "bob@polytraders"
        ],
        "artefacts": {
          "shadow_report": "sh_01HX9Y"
        }
      }
    },
    "output": {
      "label": "OperationsReport \u2014 STRATEGY_PROMOTED",
      "payload": {
        "report_id": "ops_strategyregistry_01HX9Z",
        "bot_id": "gov.strategyregistry",
        "event_type": "STRATEGY_PROMOTED",
        "slug": "sports-model",
        "to_stage": "limited_live",
        "report_kind": "OperationsReport",
        "topic": "polytraders.reports.operations"
      }
    }
  },
  "reason_codes": [
    {
      "code": "STRATEGY_PROMOTED",
      "severity": "INFO",
      "meaning": "A strategy was successfully promoted to the next stage.",
      "action": "Log and emit OperationsReport.",
      "user_message": ""
    },
    {
      "code": "STRATEGY_DEMOTED",
      "severity": "WARN",
      "meaning": "A strategy was demoted due to drift.",
      "action": "Record demotion; emit OperationsReport.",
      "user_message": ""
    },
    {
      "code": "PROMOTION_GATE_FAILED",
      "severity": "HARD_REJECT",
      "meaning": "A promotion request did not meet the required gates.",
      "action": "Reject promotion; emit alert.",
      "user_message": ""
    },
    {
      "code": "STRATEGY_DRIFT_DETECTED",
      "severity": "WARN",
      "meaning": "Drift signal received; auto-demote disabled.",
      "action": "Emit WARN; no stage change.",
      "user_message": ""
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "WARN",
      "meaning": "KillSwitch active; promotion requests are deferred.",
      "action": "Defer request; emit WARN.",
      "user_message": ""
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_gov_strategyregistry_promotions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "slug",
          "to_stage"
        ],
        "meaning": "Total successful stage promotions."
      },
      {
        "name": "polytraders_gov_strategyregistry_demotions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "slug"
        ],
        "meaning": "Total auto-demotions triggered by drift."
      },
      {
        "name": "polytraders_gov_strategyregistry_gate_failures_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "slug",
          "gate"
        ],
        "meaning": "Total promotion gate failures."
      },
      {
        "name": "polytraders_gov_strategyregistry_registry_size",
        "type": "gauge",
        "unit": "count",
        "labels": [],
        "meaning": "Current number of registered strategies."
      }
    ],
    "alerts": [
      {
        "name": "StrategyRegistryGateFailure",
        "condition": "rate(polytraders_gov_strategyregistry_gate_failures_total[10m]) > 0",
        "severity": "P2",
        "runbook": "#runbook-strategyregistry-gate"
      },
      {
        "name": "StrategyRegistryDBUnreachable",
        "condition": "absent(polytraders_gov_strategyregistry_registry_size)",
        "severity": "P1",
        "runbook": "#runbook-strategyregistry-db"
      }
    ]
  },
  "state": {
    "store": "postgres",
    "shape": "strategy_registry table: {slug, stage, prev_stage, approvers[], artefacts{}, shadow_days, updated_at}",
    "ttl": "indefinite",
    "recovery": "On restart, reload registry from Postgres immediately.",
    "size_estimate": "~1 KB per strategy entry; expected < 200 strategies"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop with serialised write lock per slug",
    "max_in_flight": 10,
    "idempotency_key": "slug + to_stage + timestamp",
    "timeout_ms": 2000,
    "backpressure": "queue",
    "locking": "Postgres row-level lock per slug on write"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "gov.backtester",
        "why": "Backtest artefact references required for promotion gates.",
        "contract": "Artefact ID must be present and valid."
      }
    ],
    "emits_to": [
      {
        "bot_id": "internal.governance_audit",
        "what": "OperationsReport on every stage transition"
      }
    ],
    "sibling": [
      {
        "bot_id": "gov.experimenttracker",
        "why": "ExperimentTracker sends drift signals that can trigger auto-demotion.",
        "contract": "Drift signal carries slug."
      }
    ],
    "external": [
      {
        "service": "Internal Postgres",
        "endpoint": "postgres://internal",
        "sla": "99.9%",
        "failure_mode": "Serve cached stage from memory; queue writes until Postgres is reachable."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": false,
    "private_key_access": "none",
    "abuse_vectors": [
      "Submitting a promotion request with forged approver identity",
      "Bypassing two-person rule by replaying a previous approval token"
    ],
    "mitigations": [
      "Approver identities are verified via internal auth; promotion events are immutably audit-logged",
      "Idempotency key prevents replay of a previous approval"
    ]
  },
  "failure_injection": [
    {
      "scenario": "DB_UNAVAILABLE",
      "how_to_inject": "Kill Postgres connection during promotion request",
      "expected_behaviour": "Request queued; WARN emitted; registry served from memory cache",
      "recovery": "Writes flush when Postgres recovers."
    },
    {
      "scenario": "GATE_FAILURE",
      "how_to_inject": "Submit promotion with shadow_days=0",
      "expected_behaviour": "PROMOTION_GATE_FAILED emitted; no stage change",
      "recovery": "Fix artefacts and resubmit."
    },
    {
      "scenario": "DRIFT_AUTO_DEMOTE",
      "how_to_inject": "Send drift signal for a limited_live strategy",
      "expected_behaviour": "Strategy demoted to shadow; STRATEGY_DEMOTED OperationsReport emitted",
      "recovery": "Re-run shadow period and re-promote."
    }
  ],
  "runbook": {
    "summary": "StrategyRegistry incidents involve DB unavailability or unexpected demotions. Every PROMOTION_GATE_FAILED is informational unless it blocks a scheduled launch.",
    "oncall_actions": [
      {
        "alert": "StrategyRegistryDBUnreachable",
        "first_action": "Check Postgres health; verify connection pool.",
        "escalate_to": "SRE on-call"
      },
      {
        "alert": "StrategyRegistryGateFailure",
        "first_action": "Identify which gate failed and which artefact is missing.",
        "escalate_to": "Governance pod lead"
      }
    ],
    "manual_overrides": [
      {
        "name": "force-stage-set",
        "how": "polytraders gov registry set-stage --slug <slug> --stage <stage> --reason <reason>",
        "when": "Emergency rollback after a bad promotion."
      }
    ],
    "healthcheck": "/internal/health/strategyregistry \u2192 green if Postgres reachable; registry loaded; last write < 1h ago; red if Postgres unreachable or registry empty"
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests for gate validation pass",
        "how_measured": "CI",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "Postgres write integration test passes",
        "how_measured": "Integration test",
        "threshold": "Pass"
      }
    ],
    "to_general_live": [
      {
        "gate": "One successful end-to-end promotion lifecycle recorded in staging",
        "how_measured": "E2E test",
        "threshold": "Pass"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "OperationsReport"
    ],
    "topics": [
      "polytraders.reports.operations"
    ],
    "cadence": "every-period",
    "retention_class": "1y",
    "sampling_rule": "batched-1/min",
    "bus_failure_action": "drop-after-buffer",
    "user_visible": "no",
    "consumes_kinds": []
  },
  "capital_impact": "Indirect",
  "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"
  }
}