#!/usr/bin/env python3
"""STOCK V.1 — the frozen substrate-ON configuration under test.

fox 2026-05-21: before the multi-day GPU campaign (hermes 3090/4090 →
qwen 3090/4090 → reasoning variants), pin the substrate-ON config so the
treatment arm cannot silently drift across the run. The control arms
(``bench/control_ab.py`` arm A = bare-model-solo,
``bench/control_sweep.py``) and the substrate-ON arm B (the full
``query()`` pipeline) must all measure the SAME substrate every cell, or
the substrate-OFF-vs-ON delta is meaningless.

Two answer modes (fox 2026-05-21)
---------------------------------
STOCK V.1 is a **two-cell config family**: the substrate is characterized
under BOTH answer shapes, each frozen with its own governance hash —

  * ``quote``          — model writes prose with inline verbatim quotes;
                         bench leader on raw lexical grounding.
  * ``claim_lattice``  — structured JSON claim-lattice (the merkle-agi-dag
                         four-rung ladder); what control_ab/control_sweep
                         already drive, and the only shape with phase-3
                         reasoning-variant support built.

``answer_mode`` is therefore a swept axis, not a single pinned value.
Everything else below is frozen identically across both modes.

What V.1 is (both modes)
------------------------
The full merkle-agi-dag reverse-RAG SQD / Prometheus-σ recursive-
falsification substrate, **non-reasoning** and **non-distributed**:

  * cross-language guard / sandwich-MT / entity-mask = OFF (English-only)
  * mechanical answer-repair    = OFF (one-shot discipline)
  * quantifier caps             = reported, not applied (dry-run)
  * metacognition preflight     = label-only (no verdict gating)
  * soft-preflight LLM sidecar  = OFF (no extra round-trip)
  * claim ceiling               = 12
  * verifier content-token rules = v2-acronym-aware
  * reasoning (inference layer) = OFF  -> reasoning variants are phase 3
  * mesh / multi-witness        = OFF  -> distributed is the later fork

Phase 3 (reasoning) note
------------------------
Reasoning refs (qwen-think) layer documented overrides onto claim_lattice
inside ``control_sweep`` (cleared JSON stop sequences, 8192-token budget
for the reasoning trace, empty-output self-heal). Those overrides change
the policy, so a reasoning run has a DIFFERENT governance hash by
construction — that is correct (it is a different, phase-3 config). The
V.1 drift guard therefore covers only the non-reasoning frozen base;
reasoning runs skip the assert.

How the freeze is enforced
--------------------------
``STOCK_V1_POLICIES[mode]`` is a deep snapshot of the live
``arborist.qa.query.DEFAULT_QUERY_POLICY`` with the load-bearing knobs
re-asserted explicitly (they ARE the current defaults — a freeze, not a
change) plus the mode's ``answer_mode``. ``assert_not_drifted(mode)``
hashes the whole effective dict and fails LOUDLY if it no longer matches
the pin — a mid-campaign edit to ``DEFAULT_QUERY_POLICY`` stops the
harness rather than quietly changing what "substrate-ON" means. We do
not hand-copy the big prompt strings (they would rot against source); we
snapshot + pin the hash.
"""

from __future__ import annotations

import copy

from arborist.qa.keys import governance_policy_hash
from arborist.qa.query import DEFAULT_QUERY_POLICY

#: The knobs that DEFINE substrate-ON V.1, re-asserted explicitly so the
#: intent is readable. Every value equals the current
#: ``DEFAULT_QUERY_POLICY`` default — see the module docstring.
#: ``answer_mode`` is NOT here — it is the swept axis (see STOCK_V1_MODES).
STOCK_V1_PINS: dict = {
    "crosslang_guard_enabled": False,
    "crosslang_translate_enabled": False,
    "crosslang_entity_mask": False,
    "repair_enabled": False,
    "soft_preflight_enabled": False,
    "metacognition_block_on_contradiction": False,
    "quantifier_guard_apply_caps": False,
    "quantifier_reject_broad": False,
    "claim_lattice_max_claims_per_answer": 12,
    "content_token_rules": "v2-acronym-aware",
}

#: The two frozen answer shapes the campaign characterizes.
STOCK_V1_MODES: tuple[str, ...] = ("quote", "claim_lattice")


def policy_for(mode: str) -> dict:
    """Frozen substrate-ON policy for one answer mode: defaults + pins."""
    if mode not in STOCK_V1_MODES:
        raise ValueError(f"unknown STOCK V.1 mode {mode!r}; "
                         f"expected one of {STOCK_V1_MODES}")
    return {
        **copy.deepcopy(DEFAULT_QUERY_POLICY),
        **STOCK_V1_PINS,
        "answer_mode": mode,
    }


#: Frozen policies, one per mode.
STOCK_V1_POLICIES: dict = {m: policy_for(m) for m in STOCK_V1_MODES}

#: Inference / harness axes — NOT policy fields (they don't fold into
#: governance_policy_hash). Phases 1-2 hold these; phase 3 flips reasoning.
STOCK_V1_REASONING = False      # reasoning variants are campaign phase 3
STOCK_V1_DISTRIBUTED = False    # mesh / multi-witness is the later fork

#: The hashes that identify the campaign — one per non-reasoning mode.
#: Pinned 2026-05-21 against DEFAULT_QUERY_POLICY at commit e227bbc.
#: Re-pin (with a fox go) only when a deliberate substrate change lands.
STOCK_V1_GOVERNANCE_HASHES: dict = {
    "quote": "5b6ca4c5e754e96b7e2e8af16a8948dec8d8b2a80b9304df21b1f6d494aade4e",
    "claim_lattice": "036a4c79fd9d381a9ebf54094091c5f4e892793ae3b6dc56f70b8d4921c47455",
}


def assert_not_drifted(mode: str | None = None) -> dict:
    """Fail loudly if DEFAULT_QUERY_POLICY drifted from the V.1 pin(s).

    ``mode=None`` checks every mode; pass a single mode to check one.
    Returns ``{mode: live_hash}`` on success. Reasoning (phase-3) runs
    intentionally diverge and should NOT call this. The campaign harness
    calls this before any non-reasoning substrate-ON run.
    """
    modes = STOCK_V1_MODES if mode is None else (mode,)
    live = {}
    for m in modes:
        h = governance_policy_hash(STOCK_V1_POLICIES[m])
        live[m] = h
        pinned = STOCK_V1_GOVERNANCE_HASHES.get(m, "__PIN_ME__")
        if pinned == "__PIN_ME__":
            continue  # un-pinned bootstrap: caller prints + bakes the value
        if h != pinned:
            raise SystemExit(
                f"STOCK V.1 DRIFT ({m}): DEFAULT_QUERY_POLICY no longer "
                "hashes to the\n  pinned campaign substrate.\n"
                f"    pinned: {pinned}\n"
                f"    live:   {h}\n"
                "  Either revert the policy change, or (with a fox go) "
                "re-pin\n  STOCK_V1_GOVERNANCE_HASHES and bump the "
                "campaign to V.2."
            )
    return live


if __name__ == "__main__":
    import json

    print("STOCK V.1 substrate-ON config (two-mode family)")
    print(f"  reasoning   : {STOCK_V1_REASONING}")
    print(f"  distributed : {STOCK_V1_DISTRIBUTED}")
    for m in STOCK_V1_MODES:
        print(f"  governance_policy_hash[{m}] = "
              f"{governance_policy_hash(STOCK_V1_POLICIES[m])}")
    print("  shared pins:")
    print(json.dumps(STOCK_V1_PINS, indent=4, sort_keys=True))
