refactor(trust): ego-centric trust replaces the global Duniter membership

The global membership rule (curated bootstrap referents + sigQty/stepMax
parameters) solved sybil-proof identity for a UBI — a problem this app
doesn't have — and its screen leaked graph jargon (npub roots, parameter
steppers). Trust is now computed from the user's own position only:
you vouch / vouched by people you know (distance <=2) / vouched by N.

- TrustCubit: drop networkMember tier, referents and params; keep the
  circle rule and the 365-day vouch expiry (renewable, self-pruning).
- Delete TrustReferents, WotSettings, TrustNetworkScreen and the bundled
  referents asset; unwire injector/bootstrap/app/chat.
- New 'Your people' screen (/your-people, from the profile): who you
  vouch for (revocable) and who vouches for you, names via ProfileCache.
- i18n: wot.* removed, yourPeople.* added (en/es/pt/ast); trust.member
  removed. Kind 30777 events on relays stay fully compatible — only the
  interpretation changes.
This commit is contained in:
vjrj 2026-07-11 13:03:20 +02:00
parent 4af90876f4
commit dc7b2eee27
25 changed files with 595 additions and 863 deletions

View file

@ -1,73 +0,0 @@
import 'dart:convert';
import 'package:commons_core/commons_core.dart';
import 'package:flutter/services.dart' show rootBundle;
import '../security/secret_store.dart';
/// The web-of-trust bootstrap referents (Duniter "seeds"): identities every
/// membership calculation trusts by construction. The network's roots.
///
/// Sources, unioned: a bundled asset (`assets/trust/referents.json`, a curated
/// founding set empty until real founders are added) plus referents the user
/// adds in-app (by npub / QR), kept in the keystore. All keys are stored and
/// returned as 64-char hex. Solving cold-start honestly: no invented identities
/// ship, and a user can bootstrap their own roots.
class TrustReferents {
TrustReferents(this._store, {this.assetPath = 'assets/trust/referents.json'});
final SecretStore _store;
final String assetPath;
static const _userKey = 'tane.social.trust.referents';
List<String>? _bundledCache;
/// Every referent (bundled user-added), as hex public keys.
Future<Set<String>> all() async =>
{...await _bundled(), ...await userAdded()};
/// Referents the user added themselves (hex), for the management UI.
Future<Set<String>> userAdded() async {
final raw = await _store.read(_userKey);
if (raw == null || raw.isEmpty) return {};
return raw.split('\n').where((s) => s.isNotEmpty).toSet();
}
/// Adds a referent from an [npubOrHex]; returns its hex form. Idempotent.
/// Throws [FormatException] if it is neither an npub nor a hex key.
Future<String> add(String npubOrHex) async {
final hex = npubToHex(npubOrHex); // validates
final current = await userAdded();
if (current.add(hex)) {
await _store.write(_userKey, current.join('\n'));
}
return hex;
}
/// Removes a user-added referent (bundled ones can't be removed).
Future<void> remove(String hex) async {
final current = await userAdded()..remove(hex);
await _store.write(_userKey, current.join('\n'));
}
Future<List<String>> _bundled() async {
final cached = _bundledCache;
if (cached != null) return cached;
try {
final decoded = jsonDecode(await rootBundle.loadString(assetPath));
final list = (decoded is Map ? decoded['referents'] : decoded) as List?;
final out = <String>[];
for (final entry in list ?? const []) {
try {
out.add(npubToHex(entry as String));
} catch (_) {
// Skip a malformed entry rather than break the whole set.
}
}
return _bundledCache = out;
} catch (_) {
return _bundledCache = const []; // no/invalid asset no bundled referents
}
}
}

View file

@ -1,45 +0,0 @@
import 'package:commons_core/commons_core.dart';
import '../security/secret_store.dart';
/// Persists the web-of-trust parameters (Duniter sigQty / stepMax / validity),
/// so a young network can loosen them and tighten as it grows exactly how Ğ1
/// itself evolved. Defaults to [WotParams.duniter]. Keystore-backed.
class WotSettings {
WotSettings(this._store);
final SecretStore _store;
static const _sigQtyKey = 'tane.social.wot.sigQty';
static const _stepMaxKey = 'tane.social.wot.stepMax';
static const _validityDaysKey = 'tane.social.wot.validityDays';
/// The active parameters (any unset field falls back to the Duniter default).
Future<WotParams> params() async {
const base = WotParams.duniter;
final sigQty = await _readInt(_sigQtyKey) ?? base.sigQty;
final stepMax = await _readInt(_stepMaxKey) ?? base.stepMax;
final validityDays =
await _readInt(_validityDaysKey) ?? base.sigValidity.inDays;
return WotParams(
sigQty: sigQty,
stepMax: stepMax,
sigValidity: Duration(days: validityDays),
);
}
Future<void> save(WotParams params) async {
await _store.write(_sigQtyKey, '${params.sigQty}');
await _store.write(_stepMaxKey, '${params.stepMax}');
await _store.write(_validityDaysKey, '${params.sigValidity.inDays}');
}
/// Restores the Ğ1 reference values.
Future<void> reset() => save(WotParams.duniter);
Future<int?> _readInt(String key) async {
final raw = await _store.read(key);
if (raw == null || raw.isEmpty) return null;
return int.tryParse(raw);
}
}