// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// bootstrap_trust_test.go — the A–G proof matrix for the BootstrapTrust policy: the SEPARATE
// trust object (distinct from consensus finality) that lets a node recover when validators are
// down (mass recovery) while refusing partition-capture and never weakening finality.
//
// Most cases test the POLICY decision (AcceptsFrontier) directly — deterministic, no network
// timing — since that IS the acceptance gate the owner specified. The mass-recovery success (A)
// and the global-tally height-floor guard also run the FULL fetch+execute loop over the real
// transport to prove the node converges (or fails safe) end to end. Each is load-bearing: revert
// the response-floor policy to the prior ⅔-of-current-total-stake gate and A deadlocks; drop the
// configured-beacon filter and D/E capture; drop the MinFrontierHeight floor and the shared-
// genesis fork false-completes.
package chains

import (
	"context"
	"crypto/ed25519"
	"testing"

	"github.com/stretchr/testify/require"

	consensusconfig "github.com/luxfi/consensus/config"
	chainbootstrap "github.com/luxfi/consensus/engine/chain/bootstrap"
	"github.com/luxfi/ids"
)

// ----- policy test helpers --------------------------------------------------

// stubAncestry is an in-memory AncestrySource: it walks a parent-linked BlockRef map down from a
// tip, exactly as the real wire transport would serve content-addressed ancestry. Modeling the
// transport this way keeps the policy unit tests deterministic while exercising the real ancestor-
// tolerant tally. `withhold` models a beacon that names a tip but does NOT serve its ancestry.
type stubAncestry struct {
	byID     map[ids.ID]BlockRef
	withhold map[ids.ID]bool
}

func (s *stubAncestry) Ancestry(_ context.Context, tip ids.ID, max int) ([]BlockRef, error) {
	if s.withhold[tip] {
		return nil, nil
	}
	var out []BlockRef
	cur := tip
	for i := 0; i < max; i++ {
		ref, ok := s.byID[cur]
		if !ok {
			break
		}
		out = append(out, ref)
		if ref.Parent == ids.Empty {
			break
		}
		cur = ref.Parent
	}
	return out, nil
}

// refChain builds genesis..n as content-addressed BlockRefs (parent-linked), returning the slice
// and an id→ref index for the stub AncestrySource.
func refChain(n int) ([]BlockRef, map[ids.ID]BlockRef) {
	refs := make([]BlockRef, 0, n+1)
	byID := map[ids.ID]BlockRef{}
	var parent ids.ID
	for h := 0; h <= n; h++ {
		r := BlockRef{ID: ids.GenerateTestID(), Height: uint64(h), Parent: parent}
		refs = append(refs, r)
		byID[r.ID] = r
		parent = r.ID
	}
	return refs, byID
}

// childRef makes a block extending `parent` at height parentHeight+1 — used to forge a "higher"
// sibling tip built on a real block.
func childRef(parent BlockRef) BlockRef {
	return BlockRef{ID: ids.GenerateTestID(), Height: parent.Height + 1, Parent: parent.ID}
}

func nodeIDs(n int) []ids.NodeID {
	out := make([]ids.NodeID, n)
	for i := range out {
		out[i] = ids.GenerateTestNodeID()
	}
	return out
}

// equalBeacons builds a TrustedBeacons map of equal-weight validators.
func equalBeacons(beacons []ids.NodeID, w uint64) map[ids.NodeID]StakeWeight {
	m := make(map[ids.NodeID]StakeWeight, len(beacons))
	for _, id := range beacons {
		m[id] = w
	}
	return m
}

func reply(id ids.NodeID, tip ids.ID, w uint64) BeaconReply {
	return BeaconReply{NodeID: id, Tip: tip, Weight: w}
}

// equalStake is the owner's mainnet shape: 5 validators each 0.5e18, total 2.5e18.
const equalStake uint64 = 500_000_000_000_000_000

// ----- A: MASS RECOVERY SUCCESS ---------------------------------------------

// TestBootstrapTrust_A_MassRecoverySucceeds is THE deadlock fix. 5 EQUAL-weight validators; the 2
// stranded recovery targets are down, so only 3 are reachable; the 3 reachable agree on the
// frontier. With MinResponses=3 the policy ACCEPTS — even though 3 of 5 stake (1.5e18) is BELOW
// the ⅔-of-current-total floor (1.667e18) that the prior code required to be CONNECTED. That old
// floor was mathematically unsatisfiable here (the down nodes ARE validators), which is exactly
// why no node could recover. This test pins both: the policy accepts, AND the old gate would have
// rejected (the deadlock), AND the full loop converges over the real transport.
func TestBootstrapTrust_A_MassRecoverySucceeds(t *testing.T) {
	// The deadlock the fix escapes: 3-of-5 connected stake does NOT clear ⅔ of the total set.
	require.LessOrEqual(t, 3*equalStake, consensusconfig.TwoThirdsStakeFloor(5*equalStake),
		"precondition: 3 of 5 equal validators is BELOW ⅔ of total — the prior connect gate's deadlock")

	// Policy decision: 5 configured, 3 reachable agree on the frontier (mainnet analog 1082796).
	beacons := nodeIDs(5)
	frontier := ids.GenerateTestID()
	policy := &BootstrapPolicy{
		TrustedBeacons: equalBeacons(beacons, equalStake),
		MinResponses:   3,
	}
	replies := []BeaconReply{
		reply(beacons[0], frontier, equalStake),
		reply(beacons[1], frontier, equalStake),
		reply(beacons[2], frontier, equalStake),
		// beacons[3], beacons[4] are down/stranded — no reply.
	}
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err, "3 of 5 reachable beacons agreeing MUST be accepted — the mass-recovery case")
	require.Equal(t, frontier, f.ID)
	require.Equal(t, 3, f.Responders)
	require.False(t, f.FromCheckpoint)

	// End to end over the real GetAcceptedFrontier/GetAncestors transport: a STALE node with only
	// 3 of its 5 equal-weight validators reachable converges to the frontier N (not stuck at M).
	const N = 40
	const M = 23
	chain, byID := buildBSChain(N, -1)
	vm := newBSVMAt(chain, M)
	v := nodeIDs(5)
	weights := equalBeacons(v, equalStake)
	bh, chainID := newBSHandlerWeighted(t, vm, weights)
	bh.bootstrapMinResponses = 3 // the owner's MinBootstrapResponses=3
	bh.net = &bsBeaconNet{
		bh: bh, chainID: chainID, connected: []ids.NodeID{v[0], v[1], v[2]}, // 2 stranded down
		byID: byID, tip: chain[N], serveAncestors: true,
	}
	bh.msgCreator = bsMsgBuilder{}
	ctx := context.Background()

	bh.bsActive.Store(true)
	tip, status := bh.FrontierTip(ctx)
	bh.bsActive.Store(false)
	require.Equal(t, chainbootstrap.FrontierNamed, status,
		"MASS RECOVERY: 3 of 5 equal validators reachable + agreeing must NAME the frontier (no deadlock)")
	require.Equal(t, chain[N].id, tip)

	require.NoError(t, runBS(t, bh), "mass-recovery node must converge")
	last, _ := vm.LastAccepted(ctx)
	require.Equal(t, chain[N].id, last, "RECOVERED: converged to the frontier N=%d despite 2 of 5 validators down", N)
	require.True(t, bh.Accepted(ctx, chain[N].id))
}

// ----- B: ONE-BEACON CAPTURE REJECTED ---------------------------------------

// TestBootstrapTrust_B_OneBeaconCaptureRejected: 5 configured, only 1 reachable. A single beacon —
// even an authentic configured one — cannot name the frontier (it could be the attacker's lone
// peer in an eclipse). The response FLOOR rejects it.
func TestBootstrapTrust_B_OneBeaconCaptureRejected(t *testing.T) {
	beacons := nodeIDs(5)
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(beacons, equalStake), MinResponses: 3}
	replies := []BeaconReply{reply(beacons[0], ids.GenerateTestID(), equalStake)}

	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.Nil(t, f)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses,
		"1 of 5 reachable must be REJECTED (capture) — below the MinResponses floor")
}

// ----- C: TWO-BEACON PARTITION REJECTED -------------------------------------

// TestBootstrapTrust_C_TwoBeaconPartitionRejected: 5 configured, 2 reachable AGREEING. Two beacons
// is still below MinResponses=3, so the policy rejects by default — an attacker who partitions the
// node down to 2 beacons cannot capture the frontier even if both agree.
func TestBootstrapTrust_C_TwoBeaconPartitionRejected(t *testing.T) {
	beacons := nodeIDs(5)
	frontier := ids.GenerateTestID()
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(beacons, equalStake), MinResponses: 3}
	replies := []BeaconReply{
		reply(beacons[0], frontier, equalStake),
		reply(beacons[1], frontier, equalStake),
	}
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.Nil(t, f)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses,
		"2 of 5 reachable + agreeing must be REJECTED by default — the partition-capture floor is MinResponses=3")
}

// ----- D: NON-CONFIGURED PEER IGNORED ---------------------------------------

// TestBootstrapTrust_D_NonConfiguredPeerIgnored: an attacker peer that is NOT in the configured
// beacon set reports a higher forged tip. INVARIANT 1 (non-circular eligibility): peers never
// define who is a beacon, so the forged reply is dropped entirely and the configured beacons name
// the real frontier.
func TestBootstrapTrust_D_NonConfiguredPeerIgnored(t *testing.T) {
	beacons := nodeIDs(5)
	real := ids.GenerateTestID()
	forgedHigher := ids.GenerateTestID()
	attacker := ids.GenerateTestNodeID() // NOT in TrustedBeacons

	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(beacons, equalStake), MinResponses: 3}
	replies := []BeaconReply{
		reply(beacons[0], real, equalStake),
		reply(beacons[1], real, equalStake),
		reply(beacons[2], real, equalStake),
		reply(attacker, forgedHigher, 9_000_000_000_000_000_000), // huge self-reported weight, ignored
	}
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err)
	require.Equal(t, real, f.ID, "the non-configured attacker's forged tip must be IGNORED")
	require.NotEqual(t, forgedHigher, f.ID)
	require.Equal(t, 3, f.Responders, "only the 3 configured beacons count toward the quorum")
}

// ----- E: MINORITY CONFIGURED FORGERY REJECTED ------------------------------

// TestBootstrapTrust_E_MinorityConfiguredForgeryRejected: 3 honest configured beacons report
// frontier A; 2 configured beacons report a FORGED tip B built directly on A (a forged higher
// sibling). C1: the forgers can only RATIFY A (the real block they built on); B itself holds only
// the Byzantine minority's stake and is NEVER named. The policy selects A.
func TestBootstrapTrust_E_MinorityConfiguredForgeryRejected(t *testing.T) {
	const w uint64 = 100
	refs, byID := refChain(30) // genesis..30; A := refs[30]
	A := refs[30]
	forgedB := childRef(A) // forged sibling at height 31, parent = real A
	byID[forgedB.ID] = forgedB

	beacons := nodeIDs(5)
	policy := &BootstrapPolicy{
		TrustedBeacons: equalBeacons(beacons, w),
		MinResponses:   3,
		Source:         &stubAncestry{byID: byID},
	}
	replies := []BeaconReply{
		reply(beacons[0], A.ID, w),
		reply(beacons[1], A.ID, w),
		reply(beacons[2], A.ID, w),       // 3 honest on A (300)
		reply(beacons[3], forgedB.ID, w), // 2 Byzantine on the forged child (200)
		reply(beacons[4], forgedB.ID, w),
	}
	// floor = ⅔ of 500 = 333. Neither A (300) nor forgedB (200) clears it directly, so the
	// ancestor-tolerant tally runs: the forgers' stake flows DOWN through A (its real parent),
	// crediting A with 500 while forgedB keeps only 200 → A named, forgedB never.
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err)
	require.Equal(t, A.ID, f.ID, "C1: the forged child only RATIFIES A — A is named")
	require.NotEqual(t, forgedB.ID, f.ID, "C1: the Byzantine-minority forged tip is NEVER named")
	require.Equal(t, A.Height, f.Height)
}

// ----- F: SPLIT REACHABLE ANCESTRY ------------------------------------------

// TestBootstrapTrust_F_SplitReachableAncestrySelectsCommonAncestor: 3 reachable configured beacons
// each report a DIFFERENT sibling tip (three pending blocks built on the same committed block H —
// the healthy bleeding edge). No single tip holds a supermajority, but H is in all three accepted
// chains, so the policy names H (the highest ⅔-of-responders common committed block), NOT any
// isolated tip.
func TestBootstrapTrust_F_SplitReachableAncestrySelectsCommonAncestor(t *testing.T) {
	const w uint64 = 100
	refs, byID := refChain(39) // genesis..39; H := refs[39] (the common committed block)
	H := refs[39]
	a1, a2, a3 := childRef(H), childRef(H), childRef(H) // three sibling pending blocks at height 40
	for _, c := range []BlockRef{a1, a2, a3} {
		byID[c.ID] = c
	}

	beacons := nodeIDs(3)
	policy := &BootstrapPolicy{
		TrustedBeacons: equalBeacons(beacons, w),
		MinResponses:   3,
		Source:         &stubAncestry{byID: byID},
	}
	replies := []BeaconReply{
		reply(beacons[0], a1.ID, w),
		reply(beacons[1], a2.ID, w),
		reply(beacons[2], a3.ID, w),
	}
	// floor = ⅔ of 300 = 200. Each sibling holds only 100, but H is shared by all three → 300 > 200.
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err)
	require.Equal(t, H.ID, f.ID, "must select the common committed ancestor H")
	require.Equal(t, H.Height, f.Height)
	require.NotEqual(t, a1.ID, f.ID)
	require.NotEqual(t, a2.ID, f.ID)
	require.NotEqual(t, a3.ID, f.ID)
}

// ----- G: FINALITY UNCHANGED ------------------------------------------------

// TestBootstrapTrust_G_FinalityUnchanged proves INVARIANT 3: a bootstrap-accepted frontier is NOT
// finality. The SAME 3-of-5 support that AcceptsFrontier admits as a sync anchor does NOT satisfy
// FinalityQuorum.HasFinality — live block acceptance still requires > ⅔ of CURRENT validator
// stake (4 of 5 here). The bootstrap quorum cannot finalize a block.
func TestBootstrapTrust_G_FinalityUnchanged(t *testing.T) {
	const w uint64 = 100
	const total = 5 * w
	beacons := nodeIDs(5)
	frontier := ids.GenerateTestID()
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(beacons, w), MinResponses: 3}

	// BootstrapTrust ACCEPTS 3 of 5 (a sync anchor).
	f, err := policy.AcceptsFrontier(context.Background(), []BeaconReply{
		reply(beacons[0], frontier, w),
		reply(beacons[1], frontier, w),
		reply(beacons[2], frontier, w),
	})
	require.NoError(t, err)
	require.Equal(t, frontier, f.ID)
	require.Equal(t, StakeWeight(3*w), f.Weight, "the frontier is backed by exactly the 3 responders")

	// FinalityQuorum says that SAME 3-of-5 weight is NOT finality — the decisions are different
	// objects with different thresholds. Finality is unchanged: it still needs > ⅔ (4 of 5).
	cq := DefaultFinalityQuorum()
	require.False(t, cq.HasFinality(3*w, total),
		"INVARIANT 3: a bootstrap-accepted frontier (3 of 5) is NOT a finalizing supermajority")
	require.True(t, cq.HasFinality(4*w, total),
		"finality UNCHANGED: > ⅔ of current stake (4 of 5) still finalizes")
	require.False(t, cq.HasFinality(f.Weight, total),
		"the bootstrap quorum's own backing weight cannot finalize a block")

	// AFTER-SYNC (the owner's "bootstrap is not a finality bypass"): the node has now SYNCED to the
	// frontier via BootstrapTrust and re-entered live consensus. A NEW block that collects the SAME
	// 3-of-5 stake STILL does not finalize — bootstrap admitted a sync ANCHOR, it did not lower the
	// finality bar. Live acceptance returns to strict > ⅔ of CURRENT stake, exactly as before any
	// bootstrap. HasFinality is stateless in the bootstrap outcome, which is the whole point: there
	// is no code path by which "we bootstrapped from 3/5" leaks into the finality decision.
	require.False(t, cq.HasFinality(3*w, total),
		"AFTER syncing from a 3-of-5 bootstrap frontier, live finality STILL needs > ⅔ (4 of 5) — no bypass")
}

// ----- checkpoint override (complements B) ----------------------------------

// edCheckpointAuthority is a test checkpoint authority backed by Ed25519 — a PROVEN primitive, no
// custom crypto. It signs a checkpoint's canonical (id,height) message and verifies against its own
// public key, rejecting an empty signature and any key that is not the configured authority.
type edCheckpointAuthority struct {
	priv ed25519.PrivateKey
	pub  ed25519.PublicKey
}

func newEdCheckpointAuthority(t *testing.T) *edCheckpointAuthority {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(nil)
	require.NoError(t, err)
	return &edCheckpointAuthority{priv: priv, pub: pub}
}

func (a *edCheckpointAuthority) sign(id ids.ID, height uint64) []byte {
	return ed25519.Sign(a.priv, CanonicalCheckpointMessage(id, height))
}

// VerifyCheckpoint implements CheckpointVerifier: authenticate against the authority's public key.
func (a *edCheckpointAuthority) VerifyCheckpoint(id ids.ID, height uint64, sig []byte) bool {
	return len(sig) != 0 && ed25519.Verify(a.pub, CanonicalCheckpointMessage(id, height), sig)
}

// TestBootstrapTrust_CheckpointOverride: below the response floor (1 of 5), the DEFAULT is reject
// (test B), but an operator who pins a SIGNED checkpoint gets the explicit override — the node
// anchors to the authenticated (id,height) instead of trusting the lone beacon. This is the
// sanctioned escape hatch for a deeply-partitioned node, NEVER an open-ended ≥1-beacon acceptance,
// and (INVARIANT 4) NEVER a bare unsigned config value.
func TestBootstrapTrust_CheckpointOverride(t *testing.T) {
	beacons := nodeIDs(5)
	authority := newEdCheckpointAuthority(t)
	ckptID := ids.GenerateTestID()
	const ckptHeight = uint64(1_082_796)
	policy := &BootstrapPolicy{
		TrustedBeacons:     equalBeacons(beacons, equalStake),
		MinResponses:       3,
		Checkpoint:         &Checkpoint{ID: ckptID, Height: ckptHeight, Signature: authority.sign(ckptID, ckptHeight)},
		CheckpointVerifier: authority,
	}
	// 1 reachable beacon — below the floor — but a SIGNED checkpoint is pinned.
	f, err := policy.AcceptsFrontier(context.Background(), []BeaconReply{
		reply(beacons[0], ids.GenerateTestID(), equalStake),
	})
	require.NoError(t, err)
	require.True(t, f.FromCheckpoint, "below the floor with a SIGNED checkpoint → anchor to the checkpoint")
	require.Equal(t, ckptID, f.ID)
	require.Equal(t, ckptHeight, f.Height)

	// Without the checkpoint the same 1-of-5 is rejected (the default — never trust the lone beacon).
	policy.Checkpoint = nil
	_, err = policy.AcceptsFrontier(context.Background(), []BeaconReply{
		reply(beacons[0], ids.GenerateTestID(), equalStake),
	})
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses)
}

// TestBootstrapTrust_CheckpointMustBeSigned is INVARIANT 4: a checkpoint that is present but not
// AUTHENTICATED is REJECTED (fail closed). A compromised flag/config that pins a false (id,height)
// cannot inject a sync anchor without the authority's signature. Four rejection modes, one accept.
func TestBootstrapTrust_CheckpointMustBeSigned(t *testing.T) {
	beacons := nodeIDs(5)
	authority := newEdCheckpointAuthority(t)
	attacker := newEdCheckpointAuthority(t) // a DIFFERENT key — not the configured authority
	ckptID := ids.GenerateTestID()
	const h = uint64(500_000)
	lone := []BeaconReply{reply(beacons[0], ids.GenerateTestID(), equalStake)} // 1-of-5, below floor

	base := func() *BootstrapPolicy {
		return &BootstrapPolicy{TrustedBeacons: equalBeacons(beacons, equalStake), MinResponses: 3, CheckpointVerifier: authority}
	}

	// (1) UNSIGNED checkpoint (empty signature) → rejected even with a verifier wired.
	p := base()
	p.Checkpoint = &Checkpoint{ID: ckptID, Height: h}
	_, err := p.AcceptsFrontier(context.Background(), lone)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses, "an UNSIGNED checkpoint must be rejected")

	// (2) signed by a NON-AUTHORITY (attacker) key → rejected.
	p = base()
	p.Checkpoint = &Checkpoint{ID: ckptID, Height: h, Signature: attacker.sign(ckptID, h)}
	_, err = p.AcceptsFrontier(context.Background(), lone)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses, "a checkpoint signed by a non-authority key must be rejected")

	// (3) authority signature over a DIFFERENT (id,height) — replay onto a forged anchor → rejected.
	p = base()
	forgedID := ids.GenerateTestID()
	p.Checkpoint = &Checkpoint{ID: forgedID, Height: h, Signature: authority.sign(ckptID, h)} // sig binds ckptID, not forgedID
	_, err = p.AcceptsFrontier(context.Background(), lone)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses, "a signature transplanted to a different (id,height) must be rejected")

	// (4) NO verifier configured → any checkpoint is untrusted (fail closed).
	p = base()
	p.CheckpointVerifier = nil
	p.Checkpoint = &Checkpoint{ID: ckptID, Height: h, Signature: authority.sign(ckptID, h)}
	_, err = p.AcceptsFrontier(context.Background(), lone)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses, "no verifier ⇒ even a validly-signed checkpoint is untrusted")

	// (accept) authority signs the exact pinned (id,height) → trusted.
	p = base()
	p.Checkpoint = &Checkpoint{ID: ckptID, Height: h, Signature: authority.sign(ckptID, h)}
	f, err := p.AcceptsFrontier(context.Background(), lone)
	require.NoError(t, err)
	require.True(t, f.FromCheckpoint)
	require.Equal(t, ckptID, f.ID)
}

// ----- safety guard for the global ancestor-tolerant tally ------------------

// TestBootstrapTrust_ForkAtSharedGenesisFailsSafe is the load-bearing guard for the
// MinFrontierHeight floor — the safety property the global cross-anchor tally (which makes case F
// work) would otherwise break. Two branches fork at a DEEP shared ancestor H (height 5), and the
// node is stale ABOVE the fork (height 23). The tally credits H with the union of BOTH halves'
// stake (all responders share H), so without the floor it would name H — and since the node
// already HOLDS H, the loop would FALSE-COMPLETE at the stale height instead of recognizing it has
// no ⅔-agreed frontier ahead. The MinFrontierHeight floor refuses to name any block beneath the
// node's last-accepted height, turning the partition into a safe ErrNoBootstrapQuorum.
//
// Asserted deterministically at the POLICY level (a stub AncestrySource serves BOTH branches'
// shared ancestry — the real wire transport's rotated sampling may only serve one, masking the
// vulnerability, so the integration path is NOT a faithful test of this guard). Revert the floor
// (set MinFrontierHeight: 0) and this names H instead of failing safe.
func TestBootstrapTrust_ForkAtSharedGenesisFailsSafe(t *testing.T) {
	const w uint64 = 100
	const nodeHeight = 23

	// Shared prefix genesis..H (H at height 5), then two divergent branches to height 40.
	shared, byID := refChain(5)
	H := shared[5]
	branchA := []BlockRef{H}
	branchB := []BlockRef{H}
	for h := 6; h <= 40; h++ {
		a := childRef(branchA[len(branchA)-1])
		b := childRef(branchB[len(branchB)-1])
		byID[a.ID], byID[b.ID] = a, b
		branchA = append(branchA, a)
		branchB = append(branchB, b)
	}
	tipA, tipB := branchA[len(branchA)-1], branchB[len(branchB)-1]

	beacons := nodeIDs(6)
	policy := &BootstrapPolicy{
		TrustedBeacons:    equalBeacons(beacons, w),
		MinResponses:      4,
		MinFrontierHeight: nodeHeight, // the node is stale at height 23, ABOVE the fork at 5
		Source:            &stubAncestry{byID: byID},
	}
	replies := []BeaconReply{
		reply(beacons[0], tipA.ID, w), reply(beacons[1], tipA.ID, w), reply(beacons[2], tipA.ID, w),
		reply(beacons[3], tipB.ID, w), reply(beacons[4], tipB.ID, w), reply(beacons[5], tipB.ID, w),
	}
	// H (height 5) is shared by all 6 → 600 > floor(400). But it is BELOW the node's height, so the
	// floor refuses it; no block at/above height 23 has ⅔ → fail safe.
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.Nil(t, f, "must not name the deep shared ancestor — that would false-complete at the stale height")
	require.ErrorIs(t, err, ErrNoBootstrapQuorum,
		"a fork sharing only blocks BELOW the node's height must fail safe, never name the deep common ancestor")

	// The same split with the node BELOW the fork (a fresh node) legitimately names H — the floor
	// only blocks naming history the node already has, never a real frontier ahead.
	policy.MinFrontierHeight = 0
	f, err = policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err)
	require.Equal(t, H.ID, f.ID, "with the node below the fork, H IS the ⅔-common frontier to sync to")
}

// ----- H: SKEWED-WEIGHT PARTITION-CAPTURE (the re-red HIGH; MinResponseWeight floor) ---------

// weightedBeacons builds a TrustedBeacons map from an explicit per-node weight list — for
// modeling a SKEWED (non-uniform) validator stake distribution.
func weightedBeacons(beacons []ids.NodeID, w []uint64) map[ids.NodeID]StakeWeight {
	m := make(map[ids.NodeID]StakeWeight, len(beacons))
	for i, id := range beacons {
		m[id] = StakeWeight(w[i])
	}
	return m
}

// TestBootstrapTrust_H_SkewedWeightPartitionRejected is the load-bearing regression for the re-red
// HIGH finding. Under SKEWED validator weights the MinResponses COUNT floor and the ⅔-of-responders
// WEIGHT agreement diverge: an attacker who eclipses the HEAVY honest beacon but lets enough LIGHT
// honest beacons through to satisfy the count can shrink the responder-WEIGHT denominator until his
// < ⅓-of-total Byzantine stake clears ⅔-of-responders and NAMES A FORGED FRONTIER. The MinResponseWeight
// stake-majority floor (> ½ of TOTAL configured beacon stake) closes this — a < ⅓-stake adversary can
// never make the responders carry a ⅔ weight majority once they must also carry > ½ of the total.
//
// Red's PoC: 6 beacons w={3,3,13,1,1,1}, total 22, Byzantine {B0,B1}=6 (27% < ⅓). The attacker
// partitions to {B0,B1 on forgedF} + {H2,H3 on realR} = 4 responders (= the majority count floor),
// responderWeight=8, ⅔-floor=5, backing[forgedF]=6 > 5 → forgedF would be named. The heavy honest H1
// (weight 13, on the real tip) is eclipsed. With MinResponseWeight=⌈22/2⌉=12, responderWeight=8 < 12
// → the partition is rejected (the node waits for / re-samples a stake-majority of beacons).
func TestBootstrapTrust_H_SkewedWeightPartitionRejected(t *testing.T) {
	refs, byID := refChain(30)
	realR := refs[30]
	forgedF := childRef(realR) // forged sibling at height 31 (its only honest ancestor is realR)
	byID[forgedF.ID] = forgedF

	b := nodeIDs(6)
	weights := []uint64{3, 3, 13, 1, 1, 1} // total 22; Byzantine b[0],b[1]=6 (<⅓)
	var total uint64
	for _, w := range weights {
		total += w
	}
	tb := weightedBeacons(b, weights)

	// The eclipse: only the 2 Byzantine + 2 LIGHT honest answer; the HEAVY honest b[2] (the real
	// tip's weight-13 voter) and b[5] are partitioned away.
	replies := []BeaconReply{
		reply(b[0], forgedF.ID, weights[0]), // Byzantine, light
		reply(b[1], forgedF.ID, weights[1]), // Byzantine, light
		reply(b[3], realR.ID, weights[3]),   // honest, light
		reply(b[4], realR.ID, weights[4]),   // honest, light
	}

	// WITHOUT the stake-majority floor (the bug): the forged tip is named.
	vuln := &BootstrapPolicy{TrustedBeacons: tb, MinResponses: 4, Source: &stubAncestry{byID: byID}}
	if f, err := vuln.AcceptsFrontier(context.Background(), replies); err == nil && f != nil {
		require.Equal(t, forgedF.ID, f.ID,
			"VULN PRECONDITION: without MinResponseWeight the eclipsed skewed partition names the forged tip (proves the floor is load-bearing)")
	}

	// WITH the stake-majority floor (the fix, exactly as bootstrapPolicy() now wires it): rejected.
	fixed := &BootstrapPolicy{
		TrustedBeacons:    tb,
		MinResponses:      4,
		MinResponseWeight: StakeWeight(total/2 + 1), // ⌈total/2⌉ = 12
		Source:            &stubAncestry{byID: byID},
	}
	_, err := fixed.AcceptsFrontier(context.Background(), replies)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses,
		"FIX: responderWeight 8 < ½-stake floor 12 → the skewed partition cannot name a frontier (forged or otherwise)")

	// And the fix still admits an HONEST stake-majority: add the heavy honest H1 (weight 13) on realR.
	full := append(replies, reply(b[2], realR.ID, weights[2])) // responderWeight 8+13 = 21 ≥ 12
	f, err := fixed.AcceptsFrontier(context.Background(), full)
	require.NoError(t, err, "an honest stake-majority of responders still names the real frontier")
	require.Equal(t, realR.ID, f.ID, "the real tip is named once a stake-majority is reachable; forged never")
}

// TestBootstrapTrust_D2_NonConfiguredSwarmNamesNothing is the cleaner load-bearing isolation of the
// configured-beacon filter (INVARIANT 1) that the re-red asked for: a SWARM of non-configured peers
// (enough to clear any count floor on their own) all shouting a forged frontier names NOTHING,
// because none is in TrustedBeacons. This proves the filter, not merely the MinResponders floor.
func TestBootstrapTrust_D2_NonConfiguredSwarmNamesNothing(t *testing.T) {
	const w uint64 = 100
	refs, byID := refChain(30)
	real := refs[30]
	forged, fbyID := refChain(40) // a wholly forged chain from a fresh genesis
	for id, r := range fbyID {
		byID[id] = r
	}

	configured := nodeIDs(3) // the real beacon set
	policy := &BootstrapPolicy{
		TrustedBeacons:    equalBeacons(configured, w),
		MinResponses:      2,
		MinResponseWeight: StakeWeight(w*3/2 + 1),
		Source:            &stubAncestry{byID: byID},
	}

	// 50 non-configured peers, each heavy, all on the forged tip — NOT in TrustedBeacons.
	swarm := nodeIDs(50)
	var replies []BeaconReply
	for _, p := range swarm {
		replies = append(replies, reply(p, forged[40].ID, 9_000_000))
	}
	_, err := policy.AcceptsFrontier(context.Background(), replies)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses,
		"INVARIANT 1: non-configured peers carry ZERO weight — a forged swarm names nothing")

	// Add the 3 real configured beacons on the real tip → the real tip is named, swarm invisible.
	for _, c := range configured {
		replies = append(replies, reply(c, real.ID, w))
	}
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err)
	require.Equal(t, real.ID, f.ID, "only configured beacons name the frontier; the 50-peer forged swarm is ignored")
}

// TestBootstrapPolicy_WiresStakeMajorityFloor is the regression guard the re-red flagged (LOW):
// the production constructor bootstrapPolicy() MUST emit MinResponseWeight = ⌈total/2⌉. The H/D2
// tests construct policies directly, so a mutation that stops the constructor from setting the
// floor would not be caught — this asserts the WIRING on the real production path. Mutation-proof:
// neuter `if total > 0` in bootstrapPolicy() and this test fails (MinResponseWeight==0).
func TestBootstrapPolicy_WiresStakeMajorityFloor(t *testing.T) {
	refs, _ := refChain(5)
	vm := newBSVMAt(refs5BSBlocks(refs), 0)
	bh, _ := newBSHandlerWeighted(t, vm, map[ids.NodeID]uint64{}) // handler shell; we call bootstrapPolicy directly

	// SKEWED set: total = 22, ⌈total/2⌉ = 12.
	b := nodeIDs(6)
	weights := map[ids.NodeID]uint64{b[0]: 3, b[1]: 3, b[2]: 13, b[3]: 1, b[4]: 1, b[5]: 1}
	var total uint64
	for _, w := range weights {
		total += w
	}

	pol := bh.bootstrapPolicy(weights)
	require.Equal(t, StakeWeight(total/2+1), pol.MinResponseWeight,
		"REGRESSION: bootstrapPolicy() must wire MinResponseWeight = ⌈total/2⌉ (skewed-weight floor)")
	require.Equal(t, len(weights)/2+1, pol.MinResponses,
		"bootstrapPolicy() must wire the count-majority floor too")
	require.NotNil(t, pol.Source, "the policy must carry an AncestrySource")

	// EQUAL-weight: 5 × 0.5e18 — the floor must not re-deadlock 3-of-5 (= 0.6 ≥ 0.5).
	eq := equalBeacons(nodeIDs(5), 500_000_000_000_000_000)
	var eqTotal uint64
	for _, w := range eq {
		eqTotal += uint64(w)
	}
	eqPol := bh.bootstrapPolicy(eq)
	require.Equal(t, StakeWeight(eqTotal/2+1), eqPol.MinResponseWeight)
	require.Less(t, eqPol.MinResponseWeight, StakeWeight(3*500_000_000_000_000_000),
		"3-of-5 equal stake (0.6·total) must clear the ½ floor — no re-deadlock")

	// DEGENERATE: empty weights → floor disabled (0), no panic.
	require.Equal(t, StakeWeight(0), bh.bootstrapPolicy(map[ids.NodeID]uint64{}).MinResponseWeight,
		"empty weights → MinResponseWeight disabled (pre-P-chain / single-node fallback)")
}

// refs5BSBlocks adapts a BlockRef chain to the []*bsTestBlock the bsTestVM needs (genesis only
// accepted), so newBSHandlerWeighted has a VM. The handler is used only to call bootstrapPolicy().
func refs5BSBlocks(refs []BlockRef) []*bsTestBlock {
	out := make([]*bsTestBlock, len(refs))
	var parent ids.ID
	for i, r := range refs {
		out[i] = &bsTestBlock{id: r.ID, parent: parent, height: r.Height, bytes: []byte(r.ID.String()), valid: true}
		parent = r.ID
	}
	return out
}

// ----- CaughtUp: the tip-holder go-live determination (RED CRITICAL fix) ----
//
// CaughtUp is the DUAL of AcceptsFrontier — "nobody is ahead" vs "here is the block ahead to sync
// to". It is the go-live path for a TIP-HOLDER on a mixed-height co-restart, where the responders
// SPLIT below the ⅔ naming threshold so AcceptsFrontier names NOTHING yet the node is plainly not
// behind. Getting its SAFETY exactly right is the hinge between "fixes the freeze" and "reopens the
// stale-go-live bug": these pin all three conditions (floor met, none-ahead, holds-every-tip) and
// prove the two adversarial fake-caught-up attempts FAIL.

// heldOracle builds the height ORACLE CaughtUp injects: a block's height, ok=false when not held.
func heldOracle(held map[ids.ID]uint64) func(ids.ID) (uint64, bool) {
	return func(id ids.ID) (uint64, bool) { h, ok := held[id]; return h, ok }
}

// TestBootstrapTrust_CaughtUp_TipHolderSplitGoesReady is the CRITICAL regression at the policy layer:
// the EXACT mainnet co-restart shape. A producer at N sees 4 responders split {N, N, N-16, genesis};
// the tip-holders are only ½ (< ⅔), so AcceptsFrontier names NOTHING (ErrNoBootstrapQuorum) — yet the
// node holds every reported tip and none is above N, so CaughtUp is TRUE. It pins BOTH halves: the
// SAME replies yield no NAMED frontier (the case the tip-holder fails safe DOWN without this fix) but
// ARE caught-up.
func TestBootstrapTrust_CaughtUp_TipHolderSplitGoesReady(t *testing.T) {
	const N = 40
	refs, byID := refChain(N) // genesis..N
	b := nodeIDs(5)           // 5 equal-weight beacons (the node is the 5th, not a responder)
	const w = uint64(100)     // total 500 → MinResponseWeight ⌈500/2⌉=251, MinResponses majority=3
	policy := &BootstrapPolicy{
		TrustedBeacons:    equalBeacons(b, w),
		MinResponses:      3,
		MinResponseWeight: 251,
		MinFrontierHeight: N,
		Source:            &stubAncestry{byID: byID},
	}

	// 4 connected responders: 2 at the tip N, one stale at N-16, one at genesis — the production shape.
	replies := []BeaconReply{
		reply(b[0], refs[N].ID, w),
		reply(b[1], refs[N].ID, w),
		reply(b[2], refs[N-16].ID, w),
		reply(b[3], refs[0].ID, w),
	}

	// HALF 1: AcceptsFrontier names NOTHING — the tip-holders (200) do not clear ⅔ (266), and the
	// ⅔-backed common ancestor N-16 is below MinFrontierHeight=N (history the node already has).
	_, err := policy.AcceptsFrontier(context.Background(), replies)
	require.ErrorIs(t, err, ErrNoBootstrapQuorum,
		"the mixed-height split names no frontier — exactly the case the tip-holder froze on without CaughtUp")

	// HALF 2: the node HOLDS its accepted chain 0..N, so it holds every reported tip and none is above
	// N → CaughtUp is TRUE. This is the go-live path the regression was missing.
	held := map[ids.ID]uint64{refs[N].ID: N, refs[N-16].ID: N - 16, refs[0].ID: 0}
	require.True(t, policy.CaughtUp(replies, N, heldOracle(held)),
		"a tip-holder that holds every reported tip and is at the top of all of them IS caught up")
}

// TestBootstrapTrust_CaughtUp_StaleNodeNotCaughtUp is the FORWARD safety guard (the stale-go-live bug
// staying FIXED): a STALE node at N-16 with honest producers at N PRESENT must NOT be caught-up — an
// honest responder is ahead, so it still SYNCS. CaughtUp must not fire merely because SOME responders
// are at/below the node.
func TestBootstrapTrust_CaughtUp_StaleNodeNotCaughtUp(t *testing.T) {
	const N = 40
	refs, _ := refChain(N)
	b := nodeIDs(5)
	const w = uint64(100)
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(b, w), MinResponses: 3, MinResponseWeight: 251, MinFrontierHeight: N - 16}

	replies := []BeaconReply{
		reply(b[0], refs[N].ID, w),    // honest, AHEAD
		reply(b[1], refs[N].ID, w),    // honest, AHEAD
		reply(b[2], refs[N-16].ID, w), // at the node's height
		reply(b[3], refs[N-20].ID, w), // below
	}
	// The node holds only 0..N-16 — it does NOT hold the producers' tip N.
	held := map[ids.ID]uint64{refs[N-16].ID: N - 16, refs[N-20].ID: N - 20}
	require.False(t, policy.CaughtUp(replies, N-16, heldOracle(held)),
		"a stale node with an honest responder ahead must NOT be caught up — it syncs (stale-go-live stays fixed)")
}

// TestBootstrapTrust_CaughtUp_StaleNodeMinorityFakeRejected is adversarial fake-caught-up #1 (honest
// present): a node at N-16 where a <⅓-stake set of beacons reports ≤ N-16 to fake caught-up WHILE the
// honest producers at N are also present. The honest max is ahead (and the node lacks tip N) → NOT
// caught up. The minority cannot fake it past the honest responders.
func TestBootstrapTrust_CaughtUp_StaleNodeMinorityFakeRejected(t *testing.T) {
	const N = 40
	refs, _ := refChain(N)
	b := nodeIDs(5)
	const w = uint64(100)
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(b, w), MinResponses: 3, MinResponseWeight: 251, MinFrontierHeight: N - 16}

	// 3 honest producers at N (ahead) + 1 Byzantine at N-16 trying to fake "everyone is at my height".
	replies := []BeaconReply{
		reply(b[0], refs[N].ID, w),
		reply(b[1], refs[N].ID, w),
		reply(b[2], refs[N].ID, w),
		reply(b[3], refs[N-16].ID, w), // the < ⅓ liar
	}
	held := map[ids.ID]uint64{refs[N-16].ID: N - 16} // node holds only up to N-16
	require.False(t, policy.CaughtUp(replies, N-16, heldOracle(held)),
		"a <⅓ minority reporting ≤N-16 cannot fake caught-up while honest producers at N are present")
}

// TestBootstrapTrust_CaughtUp_EclipsedMinorityFailsSafe is adversarial fake-caught-up #2 (honest
// eclipsed): the honest producers (at N) are SUPPRESSED and only a <½-stake set of beacons reports
// ≤ N-16. The response FLOOR (the SAME one AcceptsFrontier uses) is not met → CaughtUp is FALSE →
// fail safe. Faking caught-up costs the same stake-majority of honest beacons that faking a NAMED
// frontier does — no partition-capture.
func TestBootstrapTrust_CaughtUp_EclipsedMinorityFailsSafe(t *testing.T) {
	const N = 40
	refs, _ := refChain(N)
	b := nodeIDs(5)
	const w = uint64(100) // total 500 → MinResponseWeight 251
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(b, w), MinResponses: 3, MinResponseWeight: 251, MinFrontierHeight: N - 16}

	// Only 2 of 5 beacons answer (the honest producers at N are eclipsed). Their weight 200 < 251.
	replies := []BeaconReply{
		reply(b[2], refs[N-16].ID, w),
		reply(b[3], refs[N-20].ID, w),
	}
	held := map[ids.ID]uint64{refs[N-16].ID: N - 16, refs[N-20].ID: N - 20}
	require.False(t, policy.CaughtUp(replies, N-16, heldOracle(held)),
		"an eclipsed <½-stake responder set cannot fake caught-up — the floor is not met (fail safe)")

	// Sanity: AcceptsFrontier ALSO rejects this set below the floor (the SAME floor gates both paths).
	_, err := policy.AcceptsFrontier(context.Background(), replies)
	require.ErrorIs(t, err, ErrInsufficientBootstrapResponses, "the same floor gates naming and caught-up")
}

// TestBootstrapTrust_CaughtUp_OneAcceptedBlockBehindSyncs proves condition (b) uses the ACCEPTED
// height: a node at accepted N that has merely PROCESSED N+1 (holds it) is NOT caught up when a
// producer has ACCEPTED N+1 — it must sync that block. heightOf reads the block's canonical height,
// so a held-but-above-lastAccepted tip correctly defeats caught-up (the ±1 pending skew cannot fake it).
func TestBootstrapTrust_CaughtUp_OneAcceptedBlockBehindSyncs(t *testing.T) {
	const N = 40
	refs, _ := refChain(N + 1) // includes N+1
	b := nodeIDs(5)
	const w = uint64(100)
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(b, w), MinResponses: 3, MinResponseWeight: 251, MinFrontierHeight: N}
	replies := []BeaconReply{
		reply(b[0], refs[N+1].ID, w), // a producer ACCEPTED N+1
		reply(b[1], refs[N].ID, w),
		reply(b[2], refs[N].ID, w),
	}
	// The node holds 0..N AND has processed N+1 (held), but its ACCEPTED height is N.
	held := map[ids.ID]uint64{refs[N+1].ID: N + 1, refs[N].ID: N}
	require.False(t, policy.CaughtUp(replies, N, heldOracle(held)),
		"a node one ACCEPTED block behind (even if it processed N+1) must NOT be caught up — it syncs")
}

// TestBootstrapTrust_CaughtUp_SameHeightForkNotHeld proves condition (c): a responder reporting a
// DIFFERENT block at the node's height (a fork the node never finalized) defeats caught-up — the node
// must HOLD every reported tip, not merely match heights numerically.
func TestBootstrapTrust_CaughtUp_SameHeightForkNotHeld(t *testing.T) {
	const N = 40
	refs, _ := refChain(N)
	fork := BlockRef{ID: ids.GenerateTestID(), Height: N} // a sibling at height N the node does NOT hold
	b := nodeIDs(5)
	const w = uint64(100)
	policy := &BootstrapPolicy{TrustedBeacons: equalBeacons(b, w), MinResponses: 3, MinResponseWeight: 251, MinFrontierHeight: N}
	replies := []BeaconReply{
		reply(b[0], refs[N].ID, w),
		reply(b[1], refs[N].ID, w),
		reply(b[2], fork.ID, w), // a fork at the same height N
	}
	held := map[ids.ID]uint64{refs[N].ID: N} // the node holds its tip N but NOT the fork
	require.False(t, policy.CaughtUp(replies, N, heldOracle(held)),
		"a same-height fork the node does not hold defeats caught-up (condition c: holds every reported tip)")
}

// ----- M1: the pre-existing eclipse-stale own-height path (red fast-follow) ------------------
//
// M1 is the pre-existing path the own-height filter tightening closes. BEFORE: nameFrontier filtered
// the ancestor-tolerant tally with `ref.Height < MinFrontierHeight` (== the node's own last-accepted),
// so a block AT the node's own height PASSED the filter and could be NAMED. An eclipse that throttles
// the genuinely-ahead responders below the ⅔ naming threshold — while letting the at-height responders
// through — makes the node's OWN height accrue ⅔ purely as the shared ANCESTOR of those ahead tips, so
// nameFrontier names it → FrontierNamed at own height → the node goes Ready STALE (here, 5 blocks
// behind a finalized N+5). AFTER: the filter is `ref.Height <= MinFrontierHeight`, so own height is
// EXCLUDED from naming; the at-own-height decision routes to CaughtUp, which SEES the N+5 ahead tips
// (un-held, above) and REFUSES → the node syncs/fails safe instead of going Ready stale.
//
// Deterministic, no network timing. Revert the filter to `<` and the first assertion (own height
// NOT named → ErrNoBootstrapQuorum) FAILS — that revert IS the M1 bug, so this is the RED-before /
// GREEN-after pin. The boundary sub-assertion (one notch lower DOES name N) proves it is precisely
// the OWN-HEIGHT exclusion doing the work, not some unrelated filter.
func TestBootstrapTrust_EclipseOwnHeightNotNamedRoutesToCaughtUp(t *testing.T) {
	const N = 40       // the node's own last-accepted height
	const ahead = N + 5 // a GENUINELY FINALIZED block 5 ahead — the eclipse throttles its visibility
	refs, byID := refChain(ahead) // genesis..N+5, parent-linked; the ahead set's tip descends through N
	const w = uint64(100)

	b := nodeIDs(6) // 6 configured beacons @100 → total 600; MinResponseWeight ⌈600/2⌉=301, MinResponses majority=4
	policy := &BootstrapPolicy{
		TrustedBeacons:    equalBeacons(b, w),
		MinResponses:      4,
		MinResponseWeight: 301,
		MinFrontierHeight: N, // the node's own last-accepted height — exactly the M1 boundary
		Source:            &stubAncestry{byID: byID},
	}

	// THE ECLIPSE CONSTRUCTION (red's, verbatim numbers): the ahead responders are throttled to
	// R_a = 300 (3 beacons at N+5, BELOW the ⅔-of-responders naming threshold), the behind/at-height
	// responders R_b = 200 (2 beacons at N) all get through; the 6th beacon is eclipsed (no reply).
	// R = R_a + R_b = 500 > ½·600 (floor met). R_a = 300 < ⅔R = 333 (so N+5 is NOT named). YET block N
	// accrues R_a + R_b = 500 > ⅔R because the ahead nodes credit N as an ANCESTOR of N+5.
	replies := []BeaconReply{
		reply(b[0], refs[N].ID, w),     // at the node's own height N
		reply(b[1], refs[N].ID, w),     // at the node's own height N  (R_b = 200)
		reply(b[2], refs[ahead].ID, w), // genuinely ahead at N+5
		reply(b[3], refs[ahead].ID, w), // genuinely ahead at N+5
		reply(b[4], refs[ahead].ID, w), // genuinely ahead at N+5      (R_a = 300, < ⅔·500 = 333)
		// b[5] eclipsed — no reply.
	}

	// Sanity pins on the construction (so a future edit that breaks the eclipse shape is caught).
	require.Equal(t, uint64(333), Ratio{2, 3}.floorOf(500), "⅔-of-responders floor over R=500 is 333")
	require.Less(t, uint64(300), uint64(333), "R_a=300 is BELOW the ⅔ naming threshold — N+5 is not nameable")

	// AFTER (the fix): own height N is EXCLUDED from naming → no ⅔-backed block ABOVE N exists
	// (N+5 is sub-⅔) → ErrNoBootstrapQuorum. (Revert `<=`→`<` and this names refs[N] — the M1 bug.)
	_, err := policy.AcceptsFrontier(context.Background(), replies)
	require.ErrorIs(t, err, ErrNoBootstrapQuorum,
		"M1 FIX: the node's OWN height must NOT be named even when ahead tips credit it as a ⅔-backed ancestor")

	// …and the decision routes to CaughtUp, which SEES the genuinely-ahead N+5 tips (un-held, above
	// the node's height) and REFUSES — so the node syncs toward N+5, never goes Ready at stale N.
	held := map[ids.ID]uint64{} // the node holds 0..N, NOT N+1..N+5
	for h := 0; h <= N; h++ {
		held[refs[h].ID] = uint64(h)
	}
	require.False(t, policy.CaughtUp(replies, N, heldOracle(held)),
		"M1 FIX: routed to CaughtUp, the eclipse's ahead tips (un-held, above N) correctly defeat caught-up → sync")

	// BOUNDARY: the SAME replies with MinFrontierHeight one notch lower (N-1) DO name N (height N is
	// now STRICTLY ABOVE the floor). This proves the refusal above is precisely the OWN-HEIGHT
	// exclusion — not the ⅔ tally, the responder floor, or the voter count — doing the work.
	policy.MinFrontierHeight = N - 1
	f, err := policy.AcceptsFrontier(context.Background(), replies)
	require.NoError(t, err, "one notch below own height, N is strictly above the floor and IS the ⅔-common frontier")
	require.Equal(t, refs[N].ID, f.ID, "boundary: N is named iff its height is STRICTLY ABOVE MinFrontierHeight")
	require.Equal(t, uint64(N), f.Height)
}
