"""Seed the Ten31 v5 thesis into the Architect's substrate (Phase 1).

Builds the starting "living messaging source of truth" the partners iterate on in
the Thesis Workshop: a CORE line (throughline, the open Option A/B banner debate as
competing variants, the three pillars, the proof, and the voice rules) plus one
LINE PER SEGMENT carrying that segment's angle, and the segment definitions.

Content is Ten31's OWN messaging (docs/thesis-seed-v5.md) — not LP data — so it is
safe to ship in code. Everything lands as DRAFT/CANDIDATE nodes: nothing is made
canonical here (that is the partners' dual-approval action — guardrail #4).

`ensure_thesis_seed(conn)` is idempotent: it seeds ONLY when no thesis lines exist,
so it bootstraps an empty Workshop once and never clobbers partner edits afterward.
"""
import json
import sqlite3
import uuid
from datetime import datetime, timezone


def _now():
    return datetime.now(timezone.utc).replace(tzinfo=None).isoformat() + "Z"


def _eid(prefix):
    return f"{prefix}_{uuid.uuid4().hex[:16]}"


def _log(conn, action, target_id, payload):
    conn.execute(
        """INSERT INTO interaction_log
             (id, ts, actor_type, actor_id, action, target_type, target_id, payload, source, created_at)
           VALUES (?,?, 'system', 'thesis_seed', ?, 'thesis', ?, ?, 'seed', ?)""",
        (str(uuid.uuid4()), _now(), action, target_id, json.dumps(payload) if payload is not None else None, _now()),
    )


def _line(conn, line_key, name, segment_key=None, is_core=False, description=None):
    lid = _eid("thl")
    conn.execute(
        """INSERT INTO thesis_lines (id, line_key, name, segment_key, is_core, description, created_at, updated_at)
           VALUES (?,?,?,?,?,?,?,?)""",
        (lid, line_key, name, segment_key, 1 if is_core else 0, description, _now(), _now()),
    )
    _log(conn, "thesis.line_seeded", lid, {"line_key": line_key, "is_core": bool(is_core)})
    return lid


def _node(conn, line_id, parent_id, node_type, ordn, title, body, status="draft", variant_group=None):
    nid = _eid("thn")
    conn.execute(
        """INSERT INTO thesis_nodes (id, line_id, parent_id, node_type, ord, title, body, status, variant_group, created_at, updated_at)
           VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
        (nid, line_id, parent_id, node_type, float(ordn), title, body, status, variant_group, _now(), _now()),
    )
    return nid


# ── v5 content (docs/thesis-seed-v5.md) ──────────────────────────────────────

THROUGHLINE = (
    "Bitcoin, AI, and energy are three of the largest growth markets of the next decade, "
    "and the scarce links across them (cheap energy, compute, and the non-debasable reserve "
    "asset) capture disproportionate value as the megatrend runs. Fiat is being debased "
    "through structural deficits financed by monetary expansion, and AI is collapsing the "
    "marginal cost of anything reproducible toward zero. As money debases and AI commoditizes "
    "the reproducible, durable value accrues to the scarce side of this one supply chain, and "
    "the monetary premium accrues to bitcoin as the apex fixed-supply, non-debasable, "
    "verifiable reserve asset. AI is the abundance engine and bitcoin is the scarcity anchor, "
    "two faces of one megatrend. This is an asset-value and capital-flow conviction about "
    "where value accrues, and it is the specific connection very few others are making. Ten31 "
    "invests in that infrastructure with strong conviction."
)

OPTION_A = (
    "Ten31 invests in the infrastructure of scarcity. We back the bitcoin, energy, and AI "
    "companies that produce and secure the scarce resources these markets are built on."
)
OPTION_B = (
    "Ten31 invests in freedom technology. We back the bitcoin, energy, and AI companies "
    "building the foundation for a more sovereign, less centralized economy."
)

PILLAR_1 = (
    "Every one of these markets is bottlenecked on something scarce. AI and bitcoin both "
    "compete for cheap energy and compute, and each seam pairs a scarce input with an "
    "abundant one: energy with compute, debasement with bitcoin as the non-debasable reserve, "
    "AI with sovereign data ownership. As money debases and AI drives the reproducible toward "
    "zero cost, durable value accrues to the provably scarce side of that supply chain, and "
    "the monetary premium accrues to bitcoin as a reserve asset. The companies that own and "
    "supply the scarce side capture the value as demand grows. That is where we invest. (The "
    "value-accrual-to-bitcoin connection is forward-looking conviction about where value "
    "accrues, and that gap between today and that future is the opportunity.)"
)
PILLAR_2 = (
    "We invest in foundational infrastructure with real revenue: the companies that generate "
    "energy, secure capital, and power computation. Real businesses earning real money from "
    "real demand today."
)
PILLAR_3 = (
    "Founders come to us because of our experience and our genuine alignment with bitcoin. We "
    "pursue and lead opportunities exclusive to us. People in this ecosystem know our track "
    "record and want us on their side."
)

PROOF = (
    "$200M+ deployed across two funds into 30+ of the strongest companies in the space "
    "(Strike, Start9, energy and mining infrastructure). A six-year track record that includes "
    "large-scale M&A and public-markets activity unmatched by others in this space. Fund III "
    "continues the same strategy."
)

VOICE = (
    "Direct, concrete, conviction-driven. No \"betting\" language, no em dashes, no \"X, not Y\" "
    "phrasing, no kitchen-sink lists. Plain sentences a serious LP can verify in their head."
)

POSITIONING_NOTE = (
    "Open partner decision: which banner do we lead with? Option A frames Ten31 around "
    "scarcity; Option B frames it around freedom technology (a banner in the spirit of a16z's "
    "\"American Dynamism\"). Both wordings are still being refined — use the Architect to draft "
    "more options."
)

# segment_key -> (display name, definition, needs_to_hear angle)
SEGMENTS = [
    ("btc_native_hnwi", "Bitcoin-native HNWIs (OGs)",
     "Long-time bitcoin holders with significant net worth who care about the ecosystem succeeding.",
     "Bitcoin only wins if people build on it. Holding is not enough. You care about making "
     "bitcoin succeed, and so do we. We put capital behind the companies that turn bitcoin into "
     "a working economy.",
     "Don't lecture OGs or imply they don't understand bitcoin; don't suggest that simply holding is the goal."),
    ("institution", "Institutions",
     "Institutional allocators evaluating exposure to the bitcoin, energy, and AI buildout.",
     "Exposure to the bitcoin, energy, and AI buildout through a team with a six-year "
     "institutional track record, including large-scale M&A and public-markets activity unmatched "
     "by others in this space (and Grant's prior institutional experience on top of that).",
     "No hype or moonshot framing. Lead with the verifiable track record, not vision."),
    ("family_office", "Family offices",
     "Multi-generational capital seeking durable, long-horizon allocations.",
     "A long-horizon allocation grounded in real businesses, run by a team with deep credibility "
     "and a real track record.",
     "Avoid short-term or trader framing; emphasize durability, real businesses, and horizon."),
    ("smaller_accredited", "Smaller accredited ($100k)",
     "Accredited investors entering at a more accessible commitment size.",
     "The same thesis our most convicted investors back, at an accessible entry point.",
     "Don't talk down to them; it is the same thesis, just an accessible entry point."),
    ("ai_energy_operator", "AI & energy operators",
     "Operators in AI and energy who are not yet focused on bitcoin.",
     "You may not be focused on bitcoin today, and that is exactly the point. AI and bitcoin "
     "both compete for the same scarce input, cheap and flexible power, and most operators in "
     "your space are not yet positioned for that convergence. We are. We invest across the "
     "energy-to-compute stack that connects them, with mining-native fluency (interruptible "
     "load, behind-the-meter, stranded-gas-to-power) a generalist lacks.",
     "Don't assume they're bitcoin-focused and don't preach; connect bitcoin as a growing "
     "component of their world over time."),
]


def seed_v5(conn):
    """Insert the full v5 thesis (core line + per-segment lines + segments). Assumes
    the caller has confirmed the thesis is empty; uses fresh ids."""
    # ── segments table ──
    for key, name, definition, needs, avoid in SEGMENTS:
        sid = _eid("seg")
        conn.execute(
            """INSERT INTO segments (id, segment_key, name, definition, needs_to_hear, avoid, version_no, status, created_at, updated_at)
               VALUES (?,?,?,?,?,?,1,'active',?,?)""",
            (sid, key, name, definition, needs, avoid, _now(), _now()),
        )

    # ── core line ──
    core = _line(conn, "core", "Core Thesis", is_core=True,
                 description="The shared spine of the Ten31 thesis — throughline, banner, pillars, and proof.")
    root = _node(conn, core, None, "thesis_root", 0, "Ten31 — Core Thesis", "")
    _node(conn, core, root, "throughline", 1, "Throughline", THROUGHLINE)

    pos = _node(conn, core, root, "section", 2, "Positioning / Banner (open debate)", POSITIONING_NOTE)
    _node(conn, core, pos, "claim", 1, "Option A — Scarcity-forward", OPTION_A, status="candidate", variant_group="positioning")
    _node(conn, core, pos, "claim", 2, "Option B — Freedom tech as the banner", OPTION_B, status="candidate", variant_group="positioning")

    pillars = _node(conn, core, root, "section", 3, "Pillars", "")
    _node(conn, core, pillars, "claim", 1, "1. Scarcity is the whole opportunity", PILLAR_1)
    _node(conn, core, pillars, "claim", 2, "2. Foundational infrastructure with real revenue", PILLAR_2)
    _node(conn, core, pillars, "claim", 3, "3. Founders seek us out, and we lead deals others never see", PILLAR_3)

    _node(conn, core, root, "proof_point", 4, "The proof", PROOF)
    _node(conn, core, root, "section", 5, "Voice", VOICE)

    # ── per-segment lines ──
    for key, name, _definition, needs, _avoid in SEGMENTS:
        lid = _line(conn, f"seg_{key}", name, segment_key=key, is_core=False,
                    description=f"Segment-specific angle for {name}.")
        sroot = _node(conn, lid, None, "thesis_root", 0, name, "")
        _node(conn, lid, sroot, "segment_cut", 1, "Angle", needs)

    return {"core_line": core, "segments": len(SEGMENTS)}


def ensure_thesis_seed(conn):
    """Seed the v5 thesis once, only when the Workshop is empty (no thesis lines).
    Idempotent and non-destructive: never overwrites partner edits."""
    try:
        n = conn.execute("SELECT COUNT(*) FROM thesis_lines WHERE deleted_at IS NULL").fetchone()[0]
    except sqlite3.OperationalError:
        return  # thesis tables not present yet (migration 0002 not applied)
    if n:
        return
    out = seed_v5(conn)
    _log(conn, "thesis.seeded_v5", out["core_line"], {"segments": out["segments"], "source": "thesis-seed-v5"})
    conn.commit()
    print(f"[thesis] seeded v5 thesis (core line + {out['segments']} segment lines)")


# ── Architect positioning pass (2026-06-05) ──────────────────────────────────
# Generated from the Architect positioning workflow: five divergent framings of the
# core banner, each red-teamed and scored. Banners/throughlines are voice-checked
# (no em dashes). The `weakness`/`objection` are Architect red-team commentary, not
# LP-facing copy. Saved as competing CANDIDATE options alongside Option A/B so the
# partners can review and mark one up. Nothing here is canonical (guardrail #4).
POSITIONING_FRAMINGS = [
    {
        "key": "convergence",
        "score": 47,
        "title": "Option C - Convergence (Architect, 47/60)",
        "body": "Bitcoin, AI, and energy are now one supply chain. Ten31 owns the scarce links.\n\nThree of the largest growth markets of the decade have collapsed onto the same physical inputs: cheap power, raw compute, and final settlement. An AI data center is an energy problem. A bitcoin mine is an energy problem. The settlement layer underneath both is converging on the one money that cannot be printed to meet demand. Ten31 has spent six years deploying into the scarce parts of that stack, the generation, the compute, and the settlement, where supply is hard to create and demand is compounding. The convergence is already happening in physical reality. The capital markets have not priced it as one thing yet, and that lag is the entry point.",
        "weakness": "The word scarce is asserted, not yet proven. The verifiable legs (generation, compute) are exactly where Ten31 has no structural edge over Brookfield or a hyperscaler with cheaper capital, and the leg Ten31 uniquely owns (bitcoin settlement) is the unconfirmed one. Without two or three named deals where bitcoin-alignment was the actual unlock, it lands as a TED talk and goes flat with the largest checks.",
        "objection": "You're describing a correlation as if you own a position in it. The two links a skeptic can actually verify (generation, compute) are links where you have no structural advantage over a hyperscaler or energy PE shop with 100x your capital, and the one link you uniquely own (bitcoin settlement) is the speculative one. Where, specifically, in your $200M is the value that ONLY a bitcoin-aligned operator could have captured?",
    },
    {
        "key": "access",
        "score": 40,
        "title": "Option D - Access & track record (Architect, 40/60)",
        "body": "Founders bring us the deal before it exists. That access is earned, and it compounds.\n\nOver six years Ten31 has deployed more than $200M across two funds into 30+ of the strongest companies in bitcoin, energy, and AI infrastructure, including Strike, Start9, and core mining and energy assets. The best founders in this space come to us first, often before a round is public, because we understand where energy, compute, and hard money converge better than anyone writing checks. That conviction is the lens. The track record, including real M&A and public-markets outcomes, is the proof the lens works. Fund III continues the same strategy with the same deal flow, now deeper.",
        "weakness": "Proprietary deal flow is the most overclaimed and least falsifiable phrase in venture, so as a banner it lands flat and undifferentiated. Worse, leading with access quietly drops the one genuinely non-consensus idea Ten31 owns, trading the only thing that cannot be copied for the one thing every fund claims. It also needs at least one realized distribution figure to survive contact, since deployment is a spend metric, not a result.",
        "objection": "Every manager says founders come to them first and almost none can prove it. Naming Strike and Start9 proves you invested, not that you saw them before anyone else. And access without returns is just busy-ness: $200M deployed is a spend metric. Where is realized DPI, not M&A and public-markets activity as a vague gesture?",
    },
    {
        "key": "asymmetry",
        "score": 36,
        "title": "Option E - Asymmetry (Architect, 36/60)",
        "body": "The world is building AI and energy on top of a money it cannot keep. We own the supply side of the money it will have to use.\n\nAI and energy are the two largest demand sinks of the next decade, and both run on resources that are physically scarce. The money funding them is not scarce. Dollars can be printed; the power plants, the chips, and the compute cannot. Over time, infrastructure that produces real scarce output gets priced and settled in the one money that is also hard to produce, which is bitcoin. That convergence has not happened yet, and that is precisely why the assets sitting at the intersection are still cheap. Ten31 has spent six years buying the scarce supply side, energy generation, mining, and the rails that secure capital, before the market connects compute, energy, and hard money into a single trade.",
        "weakness": "It concedes its own load-bearing premise has not happened yet, so the whole return gates on an un-dated macro event the firm does not control, which an IC reads as a levered, illiquid proxy for the bitcoin price with extra steps. It is more abstract and more inside-baseball than the status quo, the exact opposite of the partners' diagnosis.",
        "objection": "You've told me WHEN the trade pays off but not THAT it will, and your own words give it away: that convergence has not happened yet. The entire return depends on a single macro event you can't date and don't control. If the line you've drawn from AI/energy demand back to bitcoin never gets drawn by the market, I don't get a re-rate, I get a levered, illiquid proxy for the bitcoin price with extra steps.",
    },
    {
        "key": "scarcity",
        "score": 35,
        "title": "Option F - Scarcity (chokepoints) (Architect, 35/60)",
        "body": "Ten31 owns the chokepoints. Bitcoin, AI, and energy all run out of the same three things, and we hold equity in the companies that supply them.\n\nEvery market everyone is excited about right now is demand racing ahead of a fixed supply of energy, compute, and hard money. You cannot will more of these into existence on a roadmap. Ten31 takes ownership positions in the private companies that produce and control that supply, so as demand for bitcoin, AI, and energy compounds, the value accrues to the things that stay scarce, and we already hold them. The non-consensus part is where this settles: we expect energy, compute, and AI infrastructure to increasingly price and clear in money that is hard to produce, which is not how it works today, and that gap between today and where it goes is the whole opportunity.",
        "weakness": "It commits overclaim-then-deflate inside a single paragraph. Chokepoint implies pricing power and a moat, but the proof supports ownership and revenue, not control of a bottleneck, and the very next breath admits the differentiated claim is not true today. A sophisticated reader pressure-tests the word against the proof and the banner converts from a fact into a forecast.",
        "objection": "The framing rests on a settlement migration you admit is not true today. Strip that out and what's proven is equity in 30+ companies that sell energy, mining, and bitcoin-financial services: a thematic portfolio, not control of a bottleneck. Do you OWN scarce supply (a checkable balance-sheet claim) or are you forecasting a non-consensus repricing? The banner conflates the two.",
    },
    {
        "key": "freedom-tech",
        "score": 28,
        "title": "Option G - Freedom technology (Architect, 28/60)",
        "body": "Freedom technology is becoming the supply layer of the real economy, and Ten31 owns the scarce side of it.\n\nFreedom technology is the hard infrastructure a less centralized economy runs on: energy that someone controls, computation that someone controls, and money that no one can dilute. The next decade's largest growth markets, bitcoin and AI, both consume the same two scarce inputs, cheap power and compute, and we expect both to increasingly settle on the hardest money available. Ten31 funds the companies that produce that scarce supply and earn revenue today from generating energy, securing capital, and powering computation. We are not financing a worldview. We are buying the constrained supply side of three markets before the demand fully arrives.",
        "weakness": "It leads with the single most polarizing, least underwritable word in the deck and then spends the rest of the paragraph apologizing for it. The disclaimer 'We are not financing a worldview' is a self-inflicted wound that names the objection and thereby plants it. It actively costs ground with the three segments the partners most fear losing: institutions, family offices, and operators.",
        "objection": "You don't have to say this isn't ideological unless the banner made the reader suspect it was. Either freedom is load-bearing (and diligence prices in political/regulatory risk) or it's decorative (strip it and you have the plain scarcity thesis, which is stronger). Why am I being made to swallow the ideology to reach the cash flows?",
    },
]


def ensure_positioning_framings(conn):
    """One-time, additive insert of the 2026-06-05 Architect positioning pass as
    competing CANDIDATE options under the core line's positioning variant group, so
    the partners can review and mark up the Architect's framings beside Option A/B.
    Idempotent via an interaction_log sentinel (survives soft-deletes and restarts);
    additive and non-canonical (guardrail #4). No-ops gracefully if the core line or
    positioning group is not present yet."""
    try:
        already = conn.execute(
            "SELECT 1 FROM interaction_log WHERE action='thesis.positioning_framings_seeded' LIMIT 1"
        ).fetchone()
    except sqlite3.OperationalError:
        return  # thesis / interaction_log tables not present yet
    if already:
        return
    row = conn.execute("SELECT id FROM thesis_lines WHERE line_key='core' AND deleted_at IS NULL").fetchone()
    if not row:
        return  # core line not seeded yet (ensure_thesis_seed runs first)
    core = row[0]
    prow = conn.execute(
        "SELECT parent_id, MAX(ord) FROM thesis_nodes "
        "WHERE line_id=? AND variant_group='positioning' AND deleted_at IS NULL",
        (core,),
    ).fetchone()
    if not prow or not prow[0]:
        return  # positioning variant group not found; do not guess a location
    parent_id, max_ord = prow[0], (prow[1] or 0.0)
    inserted = []
    for i, fr in enumerate(POSITIONING_FRAMINGS, start=1):
        body = f"{fr['body']}\n\n--- Architect red-team ({fr['score']}/60) ---\n{fr['weakness']}"
        nid = _node(conn, core, parent_id, "claim", float(max_ord) + i, fr["title"], body,
                    status="candidate", variant_group="positioning")
        _log(conn, "thesis.framing_seeded", nid,
             {"key": fr["key"], "score": fr["score"], "objection": fr["objection"]})
        inserted.append(nid)
    _log(conn, "thesis.positioning_framings_seeded", core,
         {"count": len(inserted), "source": "architect-pass-2026-06-05", "node_ids": inserted})
    conn.commit()
    print(f"[thesis] seeded {len(inserted)} Architect positioning framings into the Workshop")


# ── v2.0 spine (signal-engine workstream, 2026-06-09) ─────────────────────────
# A thesis correction from a parallel "signal-engine" workstream Grant runs: the spine
# is NOT "infrastructure settles on bitcoin" (settlement/payments — Strike's payments
# thesis died in backtest) but bitcoin as the APEX NON-DEBASABLE RESERVE ASSET, with
# debasement as the forcing function and AI as the abundance engine. Staged as CANDIDATE
# content (never canonical — guardrail #4); provenance + the "unratified, exposure
# figures unconfirmed by Grant" caveat are stated in the section node. The partners
# ratify, modify, or reject it at their working session.
THESIS_V2 = {
    "provenance": ("CANDIDATE, from the parallel signal-engine workstream (2026-06-09), NOT yet ratified by "
                   "the partners and NOT in any canonical thesis doc. It corrects the v5 spine from a "
                   "settlement/payments claim to a reserve-asset / capital-flow conviction. Conviction and "
                   "exposure levels are a working read of Grant's words; Grant confirms before anything is promoted."),
    "root": ("Fiat is being debased through structural deficits financed by monetary expansion. At the same "
             "time, AI is collapsing the marginal cost of anything reproducible toward zero. When the "
             "reproducible becomes nearly free, durable economic value migrates to what remains provably "
             "scarce and verifiable. Bitcoin is the apex form of that: a fixed-supply, non-debasable, "
             "verifiable reserve asset. AI is the abundance engine; bitcoin is the scarcity anchor. They "
             "are two faces of one megatrend."),
    "throughline": ("Bitcoin, AI, and energy are three of the largest growth markets of the next decade, and "
                    "the scarce links across them (cheap energy, compute, and the non-debasable reserve asset) "
                    "capture disproportionate value as the megatrend runs. Ten31's differentiated conviction is "
                    "the specific connection: as money debases and AI commoditizes the reproducible, value "
                    "accrues to the scarce side of this one supply chain, and the monetary premium accrues to "
                    "bitcoin as the apex non-debasable reserve asset. This is the precise claim that the scarce "
                    "inputs of these markets win and the monetary premium accrues to hard money."),
    "decomposition": ("Verifiable today: power, compute, and AI infrastructure draw on the same scarce inputs "
                      "(the bottleneck is physically co-located); fiat is being debased; bitcoin is provably "
                      "scarce (21M cap); AI is collapsing the marginal cost of reproducible output. Contrarian "
                      "and forward-looking (the unproven leg Ten31 uniquely owns): as the reproducible goes to "
                      "zero cost and money debases, durable value accrues to the provably scarce, and bitcoin "
                      "appreciates as the premier non-debasable reserve asset. This is an asset-value and "
                      "capital-flow claim about where the monetary premium accrues. Lead with the "
                      "verifiable co-location; earn the value-accrual conviction; never overclaim the rail."),
    "seams": [
        ("Seam 1: Energy and Compute",
         "AI and bitcoin both compete for the same scarce input: cheap, firm, flexible power. The companies "
         "that own and supply the scarce side capture value as demand grows. Mining-native fluency "
         "(interruptible load, behind-the-meter, stranded-gas-to-power) is a real underwriting edge a "
         "generalist lacks."),
        ("Seam 2: Debasement and Bitcoin",
         "As money debases, bitcoin is the non-debasable reserve, and the investable layer is the "
         "infrastructure to access, hold, leverage, and utilize it: custody, exchange, and especially bitcoin "
         "as pristine collateral for credit. As bitcoin "
         "credit products mature, holders borrow rather than sell, shrinking marginal supply, so scarcity "
         "amplifies."),
        ("Seam 3: AI and Data-Ownership",
         "As AI commoditizes baseline competence, profit on undifferentiated output erodes toward zero; "
         "durable margin accrues to those who own and protect their proprietary data and judgment. The "
         "investable layer is sovereign data and confidential inference: own your stack, own your inference. "
         "This is Ten31's published coherence conviction."),
    ],
}


def ensure_thesis_v2_candidate(conn):
    """Stage the v2.0 reserve-asset spine as CANDIDATE nodes under the core line so the
    partners can review it in the Workshop. One-time (interaction_log sentinel),
    additive, non-canonical (guardrail #4)."""
    try:
        already = conn.execute(
            "SELECT 1 FROM interaction_log WHERE action='thesis.v2_candidate_seeded' LIMIT 1").fetchone()
    except sqlite3.OperationalError:
        return
    if already:
        return
    row = conn.execute("SELECT id FROM thesis_lines WHERE line_key='core' AND deleted_at IS NULL").fetchone()
    if not row:
        return
    core = row[0]
    root = conn.execute("SELECT id FROM thesis_nodes WHERE line_id=? AND node_type='thesis_root' "
                        "AND deleted_at IS NULL ORDER BY ord LIMIT 1", (core,)).fetchone()
    if not root:
        return
    sec = _node(conn, core, root[0], "section", 6.5,
                "v2.0 spine — reserve asset (CANDIDATE · signal-engine workstream · unratified)",
                THESIS_V2["provenance"], status="candidate")
    _node(conn, core, sec, "claim", 1, "Root / forcing function (v2.0)", THESIS_V2["root"], status="candidate")
    _node(conn, core, sec, "throughline", 2, "Throughline (v2.0)", THESIS_V2["throughline"], status="candidate")
    _node(conn, core, sec, "claim", 3, "Verifiable vs contrarian decomposition (v2.0)",
          THESIS_V2["decomposition"], status="candidate")
    for i, (title, body) in enumerate(THESIS_V2["seams"], start=4):
        _node(conn, core, sec, "claim", float(i), title, body, status="candidate")
    _log(conn, "thesis.v2_candidate_seeded", core, {"source": "signal-engine-v2.0-2026-06-09", "section": sec})
    conn.commit()
    print("[thesis] staged v2.0 reserve-asset spine as candidate nodes in the Workshop")


def ensure_thesis_v2_promoted(conn):
    """Make the v2.0 reserve-asset spine the WORKING (approved) spine so the live Architect /
    outreach prompts (which flatten the whole node tree) stop emitting the dead settlement
    framing. DEPLOYMENT-STATE-INVARIANT: it targets nodes by structure (the throughline directly
    under the core thesis_root; the Pillar-1 claim by its stable title; the v2.0 section's
    children by ord), never by transient body text, so it produces the SAME clean result whether
    the box was seeded with the old settlement constants (pre-existing box) or the new reserve
    constants (fresh box).

    What it does, all reversibly:
      * Rewrites the core throughline body to the reserve-asset THROUGHLINE (this is the single
        canonical throughline; the v2.0 section's redundant throughline child is soft-retired).
      * Re-grounds Pillar 1 in place to the reserve-asset PILLAR_1 (the three pillars are KEPT and
        re-grounded per the v2.0 handoff section 5, not dropped).
      * Refreshes the v2.0 section's root / decomposition / seam children from the current (cleaned)
        THESIS_V2 constant, so a box that staged the section earlier with stale text is corrected,
        and promotes them to 'approved'.

    NODE-LEVEL ONLY. This NEVER sets a thesis_version to 'canonical' (guardrail #4): freezing v2.0
    as the canonical version stays the partners' dual-approval action. The v2.0 spine is still an
    unratified draft from the parallel signal-engine workstream.

    Idempotent (interaction_log sentinel). Reversible and non-destructive (guardrail #3): nothing
    is hard-deleted; the redundant throughline child is SOFT-retired (deleted_at), and the prior
    body/title/status/deleted_at of EVERY touched row is captured so revert restores it exactly."""
    try:
        if conn.execute(
                "SELECT 1 FROM interaction_log WHERE action='thesis.v2_spine_promoted' LIMIT 1").fetchone():
            return
    except sqlite3.OperationalError:
        return  # thesis / interaction_log tables not present yet
    core_row = conn.execute(
        "SELECT id FROM thesis_lines WHERE line_key='core' AND deleted_at IS NULL").fetchone()
    if not core_row:
        return  # core line not seeded yet
    core = core_row[0]
    root_row = conn.execute(
        "SELECT id FROM thesis_nodes WHERE line_id=? AND node_type='thesis_root' AND deleted_at IS NULL "
        "ORDER BY ord LIMIT 1", (core,)).fetchone()
    sec_row = conn.execute(
        "SELECT id FROM thesis_nodes WHERE line_id=? AND node_type='section' "
        "AND title LIKE 'v2.0 spine%' AND deleted_at IS NULL ORDER BY ord LIMIT 1", (core,)).fetchone()
    if not root_row or not sec_row:
        return  # v2.0 candidate not staged yet (ensure_thesis_v2_candidate runs first) -> no-op
    root_id, sec_id = root_row[0], sec_row[0]
    now = _now()
    touched = []  # prior {id, body, title, status, deleted_at} of every row we change, for exact revert

    def capture(nid):
        r = conn.execute("SELECT id, body, title, status, deleted_at FROM thesis_nodes WHERE id=?", (nid,)).fetchone()
        if r:
            touched.append({"id": r[0], "body": r[1], "title": r[2], "status": r[3], "deleted_at": r[4]})
        return r

    def set_node(nid, body=None, title=None, status=None, deleted_at="keep"):
        sets, vals = ["updated_at=?"], [now]
        if body is not None:
            sets.append("body=?"); vals.append(body)
        if title is not None:
            sets.append("title=?"); vals.append(title)
        if status is not None:
            sets.append("status=?"); vals.append(status)
        if deleted_at != "keep":
            sets.append("deleted_at=?"); vals.append(deleted_at)
        vals.append(nid)
        conn.execute(f"UPDATE thesis_nodes SET {', '.join(sets)} WHERE id=?", vals)

    # 1) Core throughline (directly under root): rewrite to the reserve-asset throughline, approved.
    th = conn.execute(
        "SELECT id FROM thesis_nodes WHERE line_id=? AND parent_id=? AND node_type='throughline' "
        "AND deleted_at IS NULL ORDER BY ord LIMIT 1", (core, root_id)).fetchone()
    if th:
        capture(th[0]); set_node(th[0], body=THROUGHLINE, status="approved")
    # 2) Pillar 1 (KEPT, re-grounded to reserve): rewrite body in place, approved.
    p1 = conn.execute(
        "SELECT id FROM thesis_nodes WHERE line_id=? AND node_type='claim' "
        "AND title LIKE '1. Scarcity is the whole opportunity%' AND deleted_at IS NULL ORDER BY ord LIMIT 1",
        (core,)).fetchone()
    if p1:
        capture(p1[0]); set_node(p1[0], body=PILLAR_1, status="approved")
    # 3) v2.0 section children: refresh from the cleaned THESIS_V2 constant + promote; retire the
    #    redundant throughline child (the core throughline above carries the canonical throughline).
    seams = THESIS_V2["seams"]
    for r in conn.execute(
            "SELECT id, node_type, ord FROM thesis_nodes WHERE parent_id=? AND deleted_at IS NULL ORDER BY ord",
            (sec_id,)).fetchall():
        nid, ntype, ordn = r[0], r[1], int(round(r[2]))
        capture(nid)
        if ntype == "throughline":
            set_node(nid, status="retired", deleted_at=now)  # redundant with the core throughline
        elif ordn == 1:
            set_node(nid, body=THESIS_V2["root"], status="approved")
        elif ordn == 3:
            set_node(nid, body=THESIS_V2["decomposition"], status="approved")
        elif ordn >= 4 and (ordn - 4) < len(seams):
            t, b = seams[ordn - 4]
            set_node(nid, title=t, body=b, status="approved")
        else:
            set_node(nid, status="approved")
    capture(sec_id); set_node(sec_id, body=THESIS_V2["provenance"], status="approved")

    _log(conn, "thesis.v2_spine_promoted", core,
         {"touched": touched, "section_id": sec_id,
          "note": "reserve-asset spine made the working approved spine at NODE level only; canonical "
                  "promotion remains the human dual-approval gate; v2.0 spine unratified pending the "
                  "Grant + Jonathan session"})
    conn.commit()
    print("[thesis] promoted v2.0 reserve-asset spine to the working approved spine (deployment-state-invariant)")


def revert_thesis_v2_promotion(conn):
    """Exact inverse of ensure_thesis_v2_promoted. Restores the captured prior body / title /
    status / deleted_at of every touched node (the down is exact with respect to logical state;
    only updated_at is re-stamped, which is standard), and clears the sentinel so promote can re-run."""
    try:
        row = conn.execute(
            "SELECT payload FROM interaction_log WHERE action='thesis.v2_spine_promoted' "
            "ORDER BY ts DESC LIMIT 1").fetchone()
    except sqlite3.OperationalError:
        return
    if not row:
        return  # never promoted
    p = json.loads(row[0])
    now = _now()
    for t in p.get("touched", []):
        conn.execute("UPDATE thesis_nodes SET body=?, title=?, status=?, deleted_at=?, updated_at=? WHERE id=?",
                     (t.get("body"), t.get("title"), t.get("status"), t.get("deleted_at"), now, t.get("id")))
    conn.execute("DELETE FROM interaction_log WHERE action='thesis.v2_spine_promoted'")
    conn.commit()
    print("[thesis] reverted v2.0 spine promotion; restored prior node bodies/titles/status exactly")
