tane/apps/app_seeds/lib/services/trust_referents.dart
vjrj e715b129f6 feat(trust): full Duniter web-of-trust membership (params + referents)
Slice 4 of Block 2. The pure rule was already parameterised; this adds the
policy, cold-start and UI around it.

- commons_core: WotParams (sigQty/stepMax/sigValidity, Duniter defaults) +
  WebOfTrust.membersWith; npubToHex helper (NIP-19 decode) for adding roots.
- WotSettings (keystore): the active parameters, configurable — a young
  network loosens them and tightens as it grows, as Ğ1 did. Defaults Duniter.
- TrustReferents: the bootstrap 'seeds' membership is measured from — a
  bundled asset (empty until real founders are curated, no invented keys)
  unioned with referents the user adds by npub/QR. Honest cold-start.
- TrustCubit: computes the full membership verdict against referents+params
  alongside the personal circle, and exposes a TrustTier (networkMember >
  inYourCircle > vouched > unknown). Certifications issued with the active
  validity (they expire and renew, Duniter rule).
- UI: chat trust badge by tier; a 'Network of trust' screen (manage roots +
  advanced params) reached from the profile. i18n en/es/pt/ast.

Tests: WotParams/membersWith, npubToHex, TrustReferents, WotSettings, and
TrustCubit tiers/membership. Resolves the WoT-parameters decision
(open-decisions §B). Trust net stays empty/undetermined until seeded — by
design; users bootstrap their own roots.
2026-07-10 21:02:08 +02:00

73 lines
2.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}