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.
This commit is contained in:
vjrj 2026-07-10 21:01:11 +02:00
parent a96049dd36
commit 4cf53f259f
29 changed files with 1111 additions and 39 deletions

View file

@ -19,6 +19,8 @@ import 'services/profile_store.dart';
import 'services/social_account_store.dart';
import 'services/social_service.dart';
import 'services/social_settings.dart';
import 'services/trust_referents.dart';
import 'services/wot_settings.dart';
import 'state/inventory_cubit.dart';
import 'state/variety_detail_cubit.dart';
import 'ui/about_screen.dart';
@ -34,6 +36,7 @@ import 'ui/offline_banner.dart';
import 'ui/profile_screen.dart';
import 'ui/settings_screen.dart';
import 'ui/theme.dart';
import 'ui/trust_network_screen.dart';
import 'ui/variety_detail_screen.dart';
/// Root widget. Provides the repositories to the tree and wires go_router:
@ -52,6 +55,8 @@ class TaneApp extends StatelessWidget {
this.profileStore,
this.profileCache,
this.socialAccounts,
this.trustReferents,
this.wotSettings,
this.inbox,
this.showIntro = false,
this.autoBackup,
@ -68,6 +73,8 @@ class TaneApp extends StatelessWidget {
profileStore,
profileCache,
socialAccounts,
trustReferents,
wotSettings,
inbox,
);
@ -98,6 +105,10 @@ class TaneApp extends StatelessWidget {
/// Optional store of the active social identity, for the profile switcher.
final SocialAccountStore? socialAccounts;
/// Web-of-trust bootstrap referents + parameters (network membership).
final TrustReferents? trustReferents;
final WotSettings? wotSettings;
/// App-wide inbox listener; drives the messages list's live refresh.
final InboxService? inbox;
final bool showIntro;
@ -119,6 +130,8 @@ class TaneApp extends StatelessWidget {
ProfileStore? profileStore,
ProfileCache? profileCache,
SocialAccountStore? socialAccounts,
TrustReferents? trustReferents,
WotSettings? wotSettings,
InboxService? inbox,
) {
return GoRouter(
@ -175,6 +188,16 @@ class TaneApp extends StatelessWidget {
settings: socialSettings,
profileStore: profileStore,
accounts: socialAccounts,
trustNetworkEnabled:
trustReferents != null && wotSettings != null,
),
),
if (trustReferents != null && wotSettings != null)
GoRoute(
path: '/trust',
builder: (context, state) => TrustNetworkScreen(
referents: trustReferents,
wotSettings: wotSettings,
),
),
if (social != null && socialSettings != null)
@ -186,6 +209,8 @@ class TaneApp extends StatelessWidget {
peerPubkey: state.pathParameters['pubkey']!,
messageStore: messageStore,
profileCache: profileCache,
trustReferents: trustReferents,
wotSettings: wotSettings,
),
),
GoRoute(