Name a voucher by what you verified, not by what it announces now

The systematic pass over every trust-seal rendering site turned up one
place the patch had not reached. The fingerprint sheet's voucher list is
a trust attribution one level out — "people you verified vouch for this
peer" — and it was built from `claimedNickname`, the voucher's CURRENT
name. So Eve gets verified as "ravi", vouches for Mallory, renames to
"medic", and Mallory's sheet reads "vouched by medic". The vouch is
real and the key is trusted; the name beside it was never verified,
which is exactly what the rest of this branch is about.

Order is petname, then the pinned baseline, then the claimed nickname.
The petname first because it is your own label and nothing on the
network can influence it; the claimed nickname last as the same
fail-open used everywhere else here, for vouchers recorded before
baselines existed.

The test covers the prerequisite — that the baseline outlives the
rename, so the sheet has "ravi" to show — rather than the view itself,
which needs a ChatViewModel this harness cannot build. Reverting the
VerificationModel change alone would not fail it; said plainly rather
than dressed up as coverage it does not have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shubham Bhandari 2026-09-15 18:43:22 +08:00
parent 83eb89e8f9
commit e2e929b60f
2 changed files with 38 additions and 0 deletions

View File

@ -162,11 +162,30 @@ final class VerificationModel: ObservableObject {
} else {
vouchers = []
}
// Each voucher is named by the name YOU VERIFIED IT UNDER, not by
// whatever it announces now.
//
// This list is a trust attribution — "people you verified vouch for
// this peer" — so it has the same problem as a seal, one level out. Use
// the live name and a voucher that renamed is attributed under its new
// one: Eve gets verified as "ravi", vouches for Mallory, renames to
// "medic", and Mallory's sheet reads "vouched by medic". The vouch is
// real and the key is trusted; the NAME beside it was never verified,
// which is the whole subject of this change.
//
// Order: a petname first, because it is your own label and nothing on
// the network can influence it; then the pinned baseline; then the
// claimed nickname, for vouchers recorded before baselines existed —
// the same fail-open as everywhere else here.
let voucherNames = vouchers.compactMap { record -> String? in
guard let social = chatViewModel.identityManager.getSocialIdentity(for: record.voucherFingerprint) else {
return nil
}
if let petname = social.localPetname, !petname.isEmpty { return petname }
if let pinned = chatViewModel.identityManager.trustedNickname(
fingerprint: record.voucherFingerprint), !pinned.isEmpty {
return pinned
}
return social.claimedNickname.isEmpty ? nil : social.claimedNickname
}

View File

@ -350,6 +350,25 @@ struct SecureIdentityStateManagerNicknameBindingTests {
"while the stored verification is untouched — re-verifying is the recovery")
}
@Test
func aVoucherIsNamedByTheNameItWasVerifiedUnder() {
// The voucher list in the fingerprint sheet is a trust attribution, so
// it has the seal's problem one level out: name a voucher by what it
// announces NOW and a renamed voucher is credited under the new name.
// Eve verified as "ravi", vouches for Mallory, renames to "medic", and
// Mallory's sheet reads "vouched by medic".
//
// The baseline outlives the rename precisely so the sheet can say
// "ravi" — the person you actually checked. Pinned at the manager,
// which is where the view reads it from.
let manager = makeManager()
announce(manager, vouchee, as: "ravi")
manager.setVerified(fingerprint: vouchee, verified: true)
announce(manager, vouchee, as: "medic")
#expect(manager.trustedNickname(fingerprint: vouchee) == "ravi",
"a voucher that renamed is still named by what it was verified as")
}
// MARK: - What counts as the same name
@Test