#!/usr/bin/env python3
"""
composite-strength — compose independence-count (k) and trust-tier into ONE checkable point, on a
lattice, such that neither axis can be inflated by gaming the other.

The problem this closes (exori, thecolony.cc): k and tier get quoted SEPARATELY — "k=2 over here,
library-verified over there" — and an adversary games them independently. Two 'distinct' roots that are
both self-attested give you k=2 at the tier FLOOR, which a relier reads as stronger than it is; or a
single root claimed at a high tier reads as strong while k=1 means one corruption breaks everything. The
honest object composes them on the SAME lattice by MEET: strength = ( k_after_collision_merge ,
tier_meet_across_the_surviving_roots ). You cannot raise it by stacking cheap witnesses (they lift k but
drag the tier-meet DOWN to their own low tier) and you cannot raise it by claiming a high tier on one
root (that lifts the tier but leaves k=1). Both levers are refused at once.

THE SUBTLETY THAT MAKES A NAIVE min() WRONG (and why this is a verifier, not a one-liner):
trust-tiers.json states the tier order is a PARTIAL order — "Do not collapse the middle band into one
rank." `bls/py_ecc` (a pairing library), `pow/2-explorer` (corroboration) and `probe-consistent` (a
re-run that decays) sit on DIFFERENT trust axes and are mutually INCOMPARABLE: a relier chooses which it
will accept. So `min(tierA, tierB)` over a total order is unsound — it silently picks one axis. If root A
rests on `bls/py_ecc` and root B on `pow/2-explorer`, a total-order min reports one of them, and a relier
who rejects THAT axis but would have accepted the other reads a strength that isn't jointly there. The
correct operation is the poset GREATEST LOWER BOUND: meet(bls/py_ecc, pow/2-explorer) = `testimony` — the
strongest tier that is <= BOTH. Under-claiming (dropping to the rank below an incomparable pair) is the
safe direction; over-claiming on an axis the relier didn't accept is the lie. --selftest witnesses this
RED: the naive total-order min DISAGREES with the meet on exactly the incomparable pair, so the test is
non-vacuous — it would pass a wrong implementation only if the meet silently became a total-order min.

This is arithmetic over declared inputs (the 'flag / re-executable' object class in the taxonomy, not
'entry / testimony'): a deterministic recompute over the roots a receipt COMMITS. It carries no clock and
wants none — re-run it and you get the same point. It does not fetch or anchor; feed it the roots a
lineage/quorum receipt already committed (each with the tier that receipt's own verifier asserted).

    python3 composite-strength.py --selftest
    python3 composite-strength.py --roots=roots.json      # [{"root":"id","tier":"merkle->checkpoint"}, ...]
Exit: 0 = a composite point was computed · 2 = malformed input / a tier not in the registry (fail closed).
"""
import os
import sys
import json
import importlib.util  # noqa: F401  (kept for house-style symmetry with sibling verifiers)

_HERE = os.path.dirname(os.path.abspath(__file__))
_TIERS_JSON = os.path.join(_HERE, "trust-tiers.json")

# The partial order, as COVERING relations (child < parent), transcribed from trust-tiers.json's
# "ordering" sentence. The tier NAME SET is asserted against trust-tiers.json in --selftest, so a tier
# renamed in the registry without updating this map is a red run (the registry is the source of truth for
# the vocabulary; this file is the source of truth for the ORDER the registry states only in prose).
_COVERS = {
    # child                : [strictly-greater parents it is covered by]
    "unconfirmed":          ["relay"],
    "relay":                ["testimony"],
    # testimony is covered by the independent-author band AND by library-verified (a distinct axis:
    # re-executed-but-borrowed authoring). library-verified is above testimony, below merkle->checkpoint,
    # and INCOMPARABLE to bls/pow/probe — sealed with exori by AUTHORING INDEPENDENCE, not effort.
    "testimony":            ["bls/py_ecc", "pow/2-explorer", "probe-consistent", "library-verified"],
    "bls/py_ecc":           ["merkle->checkpoint"],
    "pow/2-explorer":       ["merkle->checkpoint"],
    "probe-consistent":     ["merkle->checkpoint"],
    "library-verified":     ["merkle->checkpoint"],
    "merkle->checkpoint":   ["re-derivable", "ots/node"],
    "re-derivable":         [],
    "ots/node":             [],
}


def _ancestors_incl(tier):
    """Every tier >= `tier` (the up-set, reflexive) — used to compute lower bounds by intersection."""
    seen, stack = set(), [tier]
    while stack:
        t = stack.pop()
        if t in seen:
            continue
        seen.add(t)
        stack.extend(_COVERS.get(t, []))
    return seen


def _leq(a, b):
    """a <= b in the partial order (a rests on no more than b — b is at least as strong)."""
    return b in _ancestors_incl(a)


def tier_meet(a, b):
    """Greatest lower bound of two tiers in the PARTIAL order. For a comparable pair this is the weaker
    one; for an INCOMPARABLE pair (two different middle-band axes) it drops to the strongest tier below
    BOTH — never to either of them. Fails CLOSED: if the poset ever had no unique GLB for a pair, return
    the global bottom rather than invent a rank (soundness over precision)."""
    if a == b:
        return a
    if _leq(a, b):
        return a
    if _leq(b, a):
        return b
    # incomparable: lower bounds = tiers <= a AND <= b; take the unique greatest.
    lowers = [t for t in _COVERS if _leq(t, a) and _leq(t, b)]
    if not lowers:
        return "unconfirmed"
    maxima = [t for t in lowers if not any(t != u and _leq(t, u) for u in lowers)]
    if len(maxima) != 1:
        return "unconfirmed"   # fail closed: no unique meet, so claim the bottom
    return maxima[0]


def tier_meet_all(tiers):
    if not tiers:
        return "unconfirmed"
    m = tiers[0]
    for t in tiers[1:]:
        m = tier_meet(m, t)
    return m


def composite(roots):
    """roots: [{'root': <stable id>, 'tier': <tier name>}, ...]. Returns the honest composite point.

    Collision-merge FIRST (two entries with the same root id are ONE root — the lineage rule; if they
    disagree on tier, the merged root takes the MEET of them, the conservative read). Then:
        k    = number of surviving distinct roots       (independence count, a floor)
        tier = meet over the surviving roots' tiers      (joint tier, a poset GLB)
    and a note on which axis is the binding constraint, because collapsing the pair to one number would
    hide that — the small self-narrowing we refuse elsewhere."""
    merged = {}
    for r in roots:
        rid = str(r["root"])
        t = r["tier"]
        merged[rid] = tier_meet(merged[rid], t) if rid in merged else t
    k = len(merged)
    tier = tier_meet_all(list(merged.values()))
    # which lever is binding: is any single root already at the joint tier (tier-limited), or is every
    # root strictly above it so only the COUNT holds it there? (purely descriptive)
    limiting = "count" if all(_leq(tier, tv) and tv != tier for tv in merged.values()) else "tier"
    if k <= 1:
        limiting = "count"
    return {"k": k, "tier": tier, "merged_from": len(roots), "limiting_axis": limiting,
            "roots": merged}


def _fmt(res):
    if res.get("verdict") in ("INDETERMINATE", "NO_ROOTS", "MISMATCH", "NOT_A_DERIVATION"):
        return "composite: %s — %s" % (res["verdict"], res.get("note", res.get("reason", "")))
    out = ["composite strength (a point on the k × tier lattice — neither axis inflatable by the other):",
           "  k    = %d distinct root(s) after collision-merge (independence FLOOR)" % res["k"],
           "  tier = %s (poset MEET across the surviving roots — the joint tier, not either root's own)"
           % res["tier"]]
    if res["merged_from"] != res["k"]:
        out.append("  (%d declared roots collapsed to %d after same-id merge)" % (res["merged_from"], res["k"]))
    out.append("  binding constraint: the %s. %s" % (
        res["limiting_axis"],
        "Adding cheap witnesses would raise k but drag the tier-meet down to theirs; claiming a high tier "
        "on one root would raise the tier but leave k=1. Neither raises this point."))
    if res.get("tiered_from") == "lineage":
        out.append("  source: lineage-verify live roots (per-root tier from what the walk COMMITTED — a "
                   "head/entry that folds to a checkpoint is merkle->checkpoint; an external/raw/unanchored "
                   "root is testimony, so any asserted root drags the joint tier down).")
    return "\n".join(out)


# ------------------------------------------------------------------ the prod bridge
# lineage-verify walks a receipt to its committed roots and publishes k = distinct-root count (a floor,
# never a union) — but only a SCOPE figure, tier-blind. composite-strength turns that into the honest
# (k, tier) point by tiering each root by what the walk actually committed, then meeting them. This is
# the "fixtures certify the lab" bridge to production: fed lineage-verify's live output, it certifies a
# real receipt's composed strength.

def _lineage_root_tier(refkey, unanchored):
    """The tier a lineage ROOT rests on, from what lineage-verify verified — fail-safe (under-claim).
    A committed Touchstone head/entry whose inclusion folds to a checkpoint (RFC 6962, dep-free) is
    `merkle->checkpoint` (the strongest lineage-verify establishes alone; it will not claim ots/node —
    that needs the relier's own Bitcoin node). An UNANCHORED edge is asserted-not-committed, and an
    external source / raw inline datum carries no independent commitment of its own — all `testimony`."""
    if refkey in unanchored:
        return "testimony"                       # named but its derivation edge never anchored
    if refkey.startswith("head:") or refkey.startswith("rec:"):
        return "merkle->checkpoint"              # a committed head/entry the walk folded to a checkpoint
    return "testimony"                           # src:/raw:/unknown — an external claim; fail-safe down


def from_lineage(obj):
    """Bridge lineage-verify's structured output to a composite point. `obj` is what
    `lineage-verify.py --json` prints: {roots:[refkey,...], unanchored:[refkey,...], declined:[...],
    floor_k:int}. Fails closed exactly where lineage-verify does — a self-narrowed (declined) walk does
    NOT publish k, so it cannot publish a composite either."""
    roots = list(obj.get("roots") or [])
    declined = obj.get("declined") or []
    unanchored = set(obj.get("unanchored") or [])
    if declined:
        return {"verdict": "INDETERMINATE", "reason": "self_narrowed_walk",
                "note": "lineage-verify does not publish k for a depth-capped walk (a declined ref may "
                        "collapse to a shared root or fan out, so k is neither floor nor ceiling); no "
                        "composite is emitted. Raise --max-depth until nothing is declined."}
    if not roots:
        return {"verdict": "NO_ROOTS", "note": "no committed roots walked — nothing to compose."}
    res = composite([{"root": r, "tier": _lineage_root_tier(r, unanchored)} for r in roots])
    res["verdict"] = "COMPOSED"
    res["tiered_from"] = "lineage"
    # the composer's distinct-root count MUST equal lineage-verify's published floor; disagreement is a bug.
    fk = obj.get("floor_k")
    if fk is not None and fk != res["k"]:
        res["verdict"] = "MISMATCH"
        res["note"] = "composite k=%d != lineage floor_k=%d — the walk and the compose disagree on the " \
                      "distinct-root count; do not publish." % (res["k"], fk)
    return res


def main(argv):
    if "--selftest" in argv[1:]:
        return _selftest()
    lin = None
    for a in argv[1:]:
        if a.startswith("--from-lineage="):
            lin = a.split("=", 1)[1]
    if lin:
        try:
            obj = json.load(sys.stdin) if lin == "-" else json.load(open(lin))
        except Exception as e:  # noqa: BLE001
            print("could not read --from-lineage=%s (%s)" % (lin, e))
            return 2
        res = from_lineage(obj)
        print(_fmt(res))
        return 0 if res.get("verdict") == "COMPOSED" else 2   # fail closed on INDETERMINATE/NO_ROOTS/MISMATCH
    path = None
    for a in argv[1:]:
        if a.startswith("--roots="):
            path = a.split("=", 1)[1]
    if not path:
        print(__doc__)
        return 2
    try:
        roots = json.load(open(path))
    except Exception as e:  # noqa: BLE001
        print("could not read --roots=%s (%s)" % (path, e))
        return 2
    if not isinstance(roots, list) or not all(isinstance(r, dict) and "root" in r and "tier" in r for r in roots):
        print("roots must be a JSON list of {\"root\":..., \"tier\":...}")
        return 2
    bad = sorted({r["tier"] for r in roots} - set(_COVERS))
    if bad:
        print("tier(s) not in the registry (fail closed): %s" % ", ".join(bad))
        return 2
    print(_fmt(composite(roots)))
    return 0


def _selftest():
    ok = []

    # 0. the vocabulary in this file is EXACTLY trust-tiers.json's — drift is a red run, per the registry
    #    rule "every verifier's emitted tier set is a subset of it". Here we assert equality both ways.
    try:
        reg = json.load(open(_TIERS_JSON))
        reg_tiers = set(reg["tiers"])
        ok.append(("tier vocabulary matches trust-tiers.json", reg_tiers == set(_COVERS)))
    except Exception as e:  # noqa: BLE001
        ok.append(("trust-tiers.json loads", False))
        print("  (could not load trust-tiers.json: %s)" % e)

    # 1. order sanity: the chain, and the middle band is an ANTICHAIN (mutually incomparable).
    ok.append(("chain: unconfirmed < relay < testimony < merkle->checkpoint",
               _leq("unconfirmed", "relay") and _leq("relay", "testimony")
               and _leq("testimony", "merkle->checkpoint") and not _leq("testimony", "testimony") is False))
    band = ["bls/py_ecc", "pow/2-explorer", "probe-consistent"]
    incomparable = all(not _leq(a, b) and not _leq(b, a) for i, a in enumerate(band) for b in band[i + 1:])
    ok.append(("middle band is an antichain (no two comparable)", incomparable))
    ok.append(("re-derivable and ots/node are both maximal & incomparable",
               not _leq("re-derivable", "ots/node") and not _leq("ots/node", "re-derivable")))

    # 2. THE meet that a naive total-order min gets WRONG. Two incomparable middle-band tiers must meet
    #    DOWN to testimony — not to either of them. This is the non-vacuous case: a total-order min (by
    #    any rank index) would return one of the two; the poset meet returns the rank below both.
    m = tier_meet("bls/py_ecc", "pow/2-explorer")
    ok.append(("meet(bls/py_ecc, pow/2-explorer) == testimony (NOT either)", m == "testimony"))

    def _naive_min_by_rank(a, b):
        # a plausible-but-WRONG implementation: linearize the tiers and take the lower index. Included
        # ONLY to prove the test above is non-vacuous — it must DISAGREE with the meet on the antichain.
        rank = ["unconfirmed", "relay", "testimony", "bls/py_ecc", "pow/2-explorer",
                "probe-consistent", "merkle->checkpoint", "re-derivable", "ots/node"]
        return a if rank.index(a) <= rank.index(b) else b
    ok.append(("naive total-order min DISAGREES on the antichain (test is non-vacuous)",
               _naive_min_by_rank("bls/py_ecc", "pow/2-explorer") != tier_meet("bls/py_ecc", "pow/2-explorer")))

    # comparable pairs: meet is just the weaker one.
    ok.append(("meet(re-derivable, merkle->checkpoint) == merkle->checkpoint",
               tier_meet("re-derivable", "merkle->checkpoint") == "merkle->checkpoint"))
    ok.append(("meet(re-derivable, ots/node) == merkle->checkpoint (top antichain meets below)",
               tier_meet("re-derivable", "ots/node") == "merkle->checkpoint"))

    # 2b. library-verified (sealed with exori): a 4th middle-band member on the AUTHORING-INDEPENDENCE
    #     axis — testimony < library-verified < merkle->checkpoint, INCOMPARABLE to the independent band.
    ok.append(("testimony < library-verified < merkle->checkpoint",
               _leq("testimony", "library-verified") and _leq("library-verified", "merkle->checkpoint")
               and not _leq("library-verified", "testimony") and not _leq("merkle->checkpoint", "library-verified")))
    ok.append(("library-verified incomparable to the independent-author band",
               all(not _leq("library-verified", b) and not _leq(b, "library-verified") for b in band)))
    ok.append(("meet(library-verified, bls/py_ecc) == testimony (borrowed-author meets independent DOWN)",
               tier_meet("library-verified", "bls/py_ecc") == "testimony"))
    ok.append(("meet(library-verified, merkle->checkpoint) == library-verified (a real fold dominates it)",
               tier_meet("library-verified", "merkle->checkpoint") == "library-verified"))
    # exori's non-negotiable tie-breaker: sealing B must NOT move the already-sealed antichain GLB.
    ok.append(("meet(bls/py_ecc, pow/2-explorer) STILL == testimony after adding library-verified",
               tier_meet("bls/py_ecc", "pow/2-explorer") == "testimony"))

    # Mutation guard: the rejected A-placement (library-verified BELOW the whole band) MUST move a sealed
    #   result, so the placement is only real because breaking it is caught. A standalone GLB over an
    #   arbitrary covers-map (cache-free) shows A flips meet(bls,pow) off testimony; B leaves it put.
    def _glb(covers, a, b):
        def up(t):
            seen, st = set(), [t]
            while st:
                x = st.pop()
                if x not in seen:
                    seen.add(x); st += covers.get(x, [])
            return seen
        lowers = [t for t in covers if a in up(t) and b in up(t)]
        for t in lowers:                       # the lower bound that is >= every other lower bound
            if all(t in up(t2) for t2 in lowers):
                return t
        return None
    _A = {"unconfirmed": ["relay"], "relay": ["testimony"], "testimony": ["library-verified"],
          "library-verified": ["bls/py_ecc", "pow/2-explorer", "probe-consistent"],
          "bls/py_ecc": ["merkle->checkpoint"], "pow/2-explorer": ["merkle->checkpoint"],
          "probe-consistent": ["merkle->checkpoint"], "merkle->checkpoint": ["re-derivable", "ots/node"],
          "re-derivable": [], "ots/node": []}
    ok.append(("mutation guard: A-placement flips meet(bls,pow) to library-verified (B keeps it testimony)",
               _glb(_A, "bls/py_ecc", "pow/2-explorer") == "library-verified"
               and _glb(_COVERS, "bls/py_ecc", "pow/2-explorer") == "testimony"))

    # 3. the two inflation levers, each refused.
    strong = [{"root": "a", "tier": "merkle->checkpoint"}, {"root": "b", "tier": "merkle->checkpoint"},
              {"root": "c", "tier": "merkle->checkpoint"}]
    base = composite(strong)
    ok.append(("baseline: 3 strong roots -> (k=3, merkle->checkpoint)",
               base["k"] == 3 and base["tier"] == "merkle->checkpoint"))

    # lever A — stack cheap witnesses: k climbs but the tier-meet is dragged to their low tier.
    stacked = composite(strong + [{"root": "cheap%d" % i, "tier": "testimony"} for i in range(5)])
    ok.append(("stack 5 testimony roots -> k rises to 8 BUT tier meets down to testimony",
               stacked["k"] == 8 and stacked["tier"] == "testimony"))

    # lever B — one root at the top tier: tier is high but k=1 pins independence.
    single = composite([{"root": "solo", "tier": "re-derivable"}])
    ok.append(("one re-derivable root -> tier high but k=1 (count is the binding axis)",
               single["k"] == 1 and single["tier"] == "re-derivable" and single["limiting_axis"] == "count"))

    # 4. collision merge: two entries, same root id, different tiers -> ONE root at the MEET of them.
    coll = composite([{"root": "x", "tier": "re-derivable"}, {"root": "x", "tier": "testimony"},
                      {"root": "y", "tier": "merkle->checkpoint"}])
    ok.append(("same-id roots merge to one, taking the tier-meet (re-derivable & testimony -> testimony)",
               coll["k"] == 2 and coll["roots"]["x"] == "testimony"))

    # 5. an unknown tier must fail closed at the CLI boundary (checked in main); here assert meet is total
    #    over the registry so no valid pair returns something outside it.
    reg = set(_COVERS)
    total = all(tier_meet(a, b) in reg for a in reg for b in reg)
    ok.append(("meet is closed over the registry (never invents a rank)", total))

    # 6. the prod bridge: lineage-verify output -> composite, per-root tier from what the walk committed.
    #    all committed heads/entries -> merkle->checkpoint at k = distinct-root count.
    allcommitted = from_lineage({"roots": ["rec:opA:5", "rec:opB:9", "head:12:ab"], "unanchored": [],
                                 "declined": [], "floor_k": 3})
    ok.append(("bridge: 3 committed roots -> (k=3, merkle->checkpoint)",
               allcommitted["verdict"] == "COMPOSED" and allcommitted["k"] == 3
               and allcommitted["tier"] == "merkle->checkpoint"))
    # one EXTERNAL/raw root among committed ones drags the joint tier down to testimony (k unchanged).
    mixed = from_lineage({"roots": ["rec:opA:5", "rec:opB:9", "src:oracle-x"], "unanchored": [],
                          "declined": [], "floor_k": 3})
    ok.append(("bridge: one external src root meets the joint tier DOWN to testimony (k stays 3)",
               mixed["k"] == 3 and mixed["tier"] == "testimony"))
    # an UNANCHORED committed-looking root is asserted-not-committed -> testimony, drags the meet down.
    unanch = from_lineage({"roots": ["rec:opA:5", "rec:opB:9"], "unanchored": ["rec:opB:9"],
                           "declined": [], "floor_k": 2})
    ok.append(("bridge: an unanchored root is testimony, so the joint tier is testimony",
               unanch["tier"] == "testimony"))
    # fail closed: a self-narrowed (declined) walk publishes NO composite, mirroring lineage-verify.
    capped = from_lineage({"roots": ["rec:opA:5"], "unanchored": [], "declined": ["rec:opZ:3"],
                           "floor_k": 1})
    ok.append(("bridge: declined (self-narrowed) walk -> INDETERMINATE, no k published",
               capped["verdict"] == "INDETERMINATE" and "k" not in capped))
    # the composer's k MUST equal lineage-verify's published floor, or it refuses (a walk/compose disagreement).
    mism = from_lineage({"roots": ["rec:opA:5", "rec:opB:9"], "unanchored": [], "declined": [], "floor_k": 5})
    ok.append(("bridge: composite k != lineage floor_k -> MISMATCH (fail closed)",
               mism["verdict"] == "MISMATCH"))
    # same-id roots reported twice by the walk merge to one (the lineage collision rule) before k is read.
    coll_l = from_lineage({"roots": ["rec:opA:5", "rec:opA:5", "head:12:ab"], "unanchored": [],
                           "declined": [], "floor_k": 2})
    ok.append(("bridge: duplicate root ids merge to one before k (k=2, not 3)", coll_l["k"] == 2))

    for n, g in ok:
        print("  %-62s %s" % (n, "ok" if g else "FAIL"))
    bad = [n for n, g in ok if not g]
    print("\n" + ("SELFTEST FAILED: " + "; ".join(bad) if bad else
                  "SELFTEST OK — k×tier composed on the partial-order lattice; both inflation levers "
                  "refused; the antichain meet is witnessed against a wrong total-order min."))
    return 1 if bad else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
