// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
// ══════════════════════════════════════════════════════════════════════
// sharesRoot - signer independence from the key and DID graph
// ══════════════════════════════════════════════════════════════════════
//
// Independence is the SHARP metric for an evidence descriptor. Two signers
// that share a confidence backbone are not independent witnesses, no matter
// how many distinct keys or DIDs they present. Witnesses that chain to the
// same gateway root of trust are still self-attestation.
//
// This file derives that relation MECHANICALLY from a graph the verifier
// already holds: each signer is a node identified by its public key or DID,
// and `chainsTo` edges name the anchors (issuer DID, gateway root, JWKS
// origin, trust-anchor fingerprint) it depends on. Two signers `sharesRoot`
// when their anchor closures intersect. Nothing here reads an issuer-written
// field; nothing grades or scores. It answers one yes/no question per pair:
// do these two signers reduce to a common root?
//
// The relation is reflexive (a signer always shares a root with itself),
// symmetric (order does not matter), and computed over the transitive
// closure of `chainsTo` so an indirect common ancestor still collapses
// independence. It is NOT transitive across signers: A and B sharing a
// root, and B and C sharing a DIFFERENT root, does not make A and C share
// a root. Independence is a per-pair fact, not a partition.
// ══════════════════════════════════════════════════════════════════════
/**
 * Build a union-find over anchor identifiers so that `anchorEdges`
 * equivalences collapse to a single representative. This lets two anchors
 * that are really the same root count as shared even under different labels.
 */
function buildAnchorUnion(anchorEdges) {
    const parent = new Map();
    const find = (x) => {
        let root = x;
        while (parent.has(root) && parent.get(root) !== root) {
            root = parent.get(root);
        }
        // Path compression for stable, repeatable output.
        let cur = x;
        while (parent.has(cur) && parent.get(cur) !== root) {
            const next = parent.get(cur);
            parent.set(cur, root);
            cur = next;
        }
        return root;
    };
    const union = (a, b) => {
        if (!parent.has(a))
            parent.set(a, a);
        if (!parent.has(b))
            parent.set(b, b);
        const ra = find(a);
        const rb = find(b);
        if (ra !== rb) {
            // Deterministic representative: lexicographically smaller wins.
            if (ra < rb)
                parent.set(rb, ra);
            else
                parent.set(ra, rb);
        }
    };
    for (const [a, b] of anchorEdges ?? [])
        union(a, b);
    return (a) => (parent.has(a) ? find(a) : a);
}
/**
 * The anchor closure of one signer: the set of canonical root
 * representatives it reduces to. A signer with no `chainsTo` anchors its
 * own identity, so two distinct anchorless signers are independent of each
 * other but a signer always shares a root with itself.
 */
function anchorClosure(node, canonical) {
    const anchors = node.chainsTo && node.chainsTo.length > 0 ? node.chainsTo : [node.id];
    const out = new Set();
    for (const a of anchors)
        out.add(canonical(a));
    return out;
}
/**
 * sharesRoot: do two signers reduce to a common root in the key/DID graph?
 *
 * Returns the relation, including the shared anchors when they are not
 * independent. Reflexive: a signer always shares a root with itself.
 * Symmetric: the answer does not depend on argument order. Pure: identical
 * inputs always yield identical output, so the descriptor is reproducible.
 *
 * This is the only place independence is decided. It reads `id` and
 * `chainsTo` and nothing else - never an assurance level, never an
 * issuer-written field.
 */
export function sharesRoot(a, b, graph) {
    const canonical = buildAnchorUnion(graph?.anchorEdges);
    const closureA = anchorClosure(a, canonical);
    const closureB = anchorClosure(b, canonical);
    const shared = [];
    for (const anchor of closureA) {
        if (closureB.has(anchor))
            shared.push(anchor);
    }
    shared.sort();
    return {
        signerA: a.id,
        signerB: b.id,
        independent: shared.length === 0,
        sharedRoots: shared,
    };
}
/**
 * All pairwise independence relations over a signer graph, in a stable
 * order (sorted by signer id, then partner id). Self-pairs are omitted -
 * a signer is trivially non-independent of itself and that fact carries no
 * descriptor signal. Distinct signers that share an `id` collapse to one
 * relation set keyed by first occurrence.
 *
 * Pure over the graph: the same graph always produces the same relations,
 * which is what lets the advisory scalar be reproducible from the
 * descriptor alone.
 */
export function allPairwiseIndependence(graph) {
    // De-duplicate signers by id, keeping first occurrence (which carries the
    // chainsTo the verifier assembled for that identity).
    const seen = new Map();
    for (const node of graph.nodes) {
        if (!seen.has(node.id))
            seen.set(node.id, node);
    }
    const nodes = [...seen.values()].sort((x, y) => (x.id < y.id ? -1 : x.id > y.id ? 1 : 0));
    const relations = [];
    for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
            relations.push(sharesRoot(nodes[i], nodes[j], graph));
        }
    }
    return relations;
}
/**
 * Count of signers that are independent of EVERY other signer in the graph
 * (no shared root with any peer). This is a mechanical census, not a grade:
 * it just reports how many fully-independent corroborators the evidence
 * carries. A lone signer trivially counts as 1.
 */
export function independentSignerCount(graph) {
    const seen = new Map();
    for (const node of graph.nodes) {
        if (!seen.has(node.id))
            seen.set(node.id, node);
    }
    const nodes = [...seen.values()];
    if (nodes.length <= 1)
        return nodes.length;
    let count = 0;
    for (let i = 0; i < nodes.length; i++) {
        let independentOfAll = true;
        for (let j = 0; j < nodes.length; j++) {
            if (i === j)
                continue;
            if (!sharesRoot(nodes[i], nodes[j], graph).independent) {
                independentOfAll = false;
                break;
            }
        }
        if (independentOfAll)
            count++;
    }
    return count;
}
//# sourceMappingURL=shares-root.js.map