// Resolving a handle is TWO checks, in two places, and this is the second one.
//
// `Namespace.resolve` proves who MADE a claim — from the claim's own signed bytes, inside apply,
// with no state at all. What apply structurally cannot prove is that the identity owns the feed the
// claim points at: the namespace is a separate log with no access to a feed's `__founder__`. So a
// stranger CAN aim a name at someone else's feed, under their own root, and it verifies.
//
// ⛔ That lie is only detectable where the feed is held, which is what this decides. NAMING.md §3
// resolves a name to an identity ROOT and only then to a room key; skipping the second hop is what
// made claims unauthenticated in the first place.
import test from 'brittle'
import { handleVerdict } from '../lib/node.js'
const ROOT = 'a'.repeat(64)
const OTHER = 'b'.repeat(64)
const claim = (root = ROOT) => ({ handle: 'alice0', name: 'alice', number: 0, feed: 'f'.repeat(64), root })
test('the feed agrees — the handle means what it says', (t) => {
const v = handleVerdict(claim(), { device: 'd'.repeat(64), identityRoot: ROOT })
t.is(v.verdict, 'confirmed')
t.is(v.founder, ROOT, 'and the founder is reported, not merely compared')
t.is(v.handle, 'alice0', 'the claim travels through intact')
})
test('★ the feed is founded by SOMEONE ELSE — this is the attack', (t) => {
// A stranger claims `alice` pointing at a feed they do not own. The claim itself is perfectly
// valid: signed, unforgeable, un-liftable — and a lie. Nothing before this point can tell.
const v = handleVerdict(claim(OTHER), { device: 'd'.repeat(64), identityRoot: ROOT })
t.is(v.verdict, 'MISMATCH', 'refused')
t.is(v.founder, ROOT, 'and BOTH roots are surfaced, so the disagreement is legible')
t.is(v.root, OTHER)
})
test('a feed nobody founded names nobody', (t) => {
t.is(handleVerdict(claim(), null).verdict, 'unfounded', 'no founder record at all')
t.is(handleVerdict(claim(), {}).verdict, 'unfounded', 'a founder record with no root is the same')
// ⚠ Not 'confirmed'. An empty feed agreeing with everyone is how a check becomes decorative.
t.not(handleVerdict(claim(), {}).verdict, 'confirmed')
})
test('⛔ the comparison is on hex strings, and a near-miss is a MISMATCH', (t) => {
// A Buffer compared by identity here would reject everything; a loose compare would accept
// anything. Both failures are silent, so the boundary is pinned.
t.is(handleVerdict(claim(ROOT), { identityRoot: ROOT.slice(0, 63) + 'b' }).verdict, 'MISMATCH',
'one character apart is not the same identity')
t.is(handleVerdict(claim(ROOT), { identityRoot: ROOT.toUpperCase() }).verdict, 'MISMATCH',
'case is not normalised HERE — both sides come from attestation.js, which emits lower-case hex')
})