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:
parent
a96049dd36
commit
4cf53f259f
29 changed files with 1111 additions and 39 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
|
@ -10,6 +11,8 @@ import '../services/message_store.dart';
|
|||
import '../services/profile_cache.dart';
|
||||
import '../services/social_service.dart';
|
||||
import '../services/social_settings.dart';
|
||||
import '../services/trust_referents.dart';
|
||||
import '../services/wot_settings.dart';
|
||||
import '../state/messages_cubit.dart';
|
||||
import '../state/trust_cubit.dart';
|
||||
import 'theme.dart';
|
||||
|
|
@ -24,6 +27,8 @@ class ChatScreen extends StatefulWidget {
|
|||
required this.peerPubkey,
|
||||
this.messageStore,
|
||||
this.profileCache,
|
||||
this.trustReferents,
|
||||
this.wotSettings,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -37,6 +42,10 @@ class ChatScreen extends StatefulWidget {
|
|||
/// Optional cache of peer display names; null in tests.
|
||||
final ProfileCache? profileCache;
|
||||
|
||||
/// Web-of-trust bootstrap referents + parameters, for the membership verdict.
|
||||
final TrustReferents? trustReferents;
|
||||
final WotSettings? wotSettings;
|
||||
|
||||
@override
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
}
|
||||
|
|
@ -69,6 +78,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
}
|
||||
}
|
||||
final cachedName = await widget.profileCache?.name(widget.peerPubkey);
|
||||
final referents = await widget.trustReferents?.all() ?? const <String>{};
|
||||
final wotParams =
|
||||
await widget.wotSettings?.params() ?? WotParams.duniter;
|
||||
if (!mounted) {
|
||||
await session?.close();
|
||||
return;
|
||||
|
|
@ -80,8 +92,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
selfPubkey: self,
|
||||
store: widget.messageStore,
|
||||
)..start();
|
||||
final trust = TrustCubit(session?.trust,
|
||||
peerPubkey: widget.peerPubkey, selfPubkey: self);
|
||||
final trust = TrustCubit(
|
||||
session?.trust,
|
||||
peerPubkey: widget.peerPubkey,
|
||||
selfPubkey: self,
|
||||
referents: referents,
|
||||
params: wotParams,
|
||||
);
|
||||
unawaited(trust.load());
|
||||
setState(() {
|
||||
_session = session;
|
||||
|
|
@ -249,30 +266,33 @@ class _TrustBanner extends StatelessWidget {
|
|||
builder: (context, state) {
|
||||
final cubit = context.read<TrustCubit>();
|
||||
if (!cubit.isOnline || state.loading) return const SizedBox.shrink();
|
||||
final known = state.knownToYou;
|
||||
// Strongest applicable signal decides the badge (member > circle >
|
||||
// vouched > unknown).
|
||||
final (IconData icon, String label, bool strong) = switch (state.tier) {
|
||||
TrustTier.networkMember => (Icons.verified, t.trust.member, true),
|
||||
TrustTier.inYourCircle => (Icons.verified_user, t.trust.circle, true),
|
||||
TrustTier.vouched => (
|
||||
Icons.people_outline,
|
||||
t.trust.count(n: state.certifierCount),
|
||||
false,
|
||||
),
|
||||
TrustTier.unknown => (Icons.person_outline, t.trust.none, false),
|
||||
};
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: seedPrimaryContainer.withValues(alpha: 0.5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
known ? Icons.verified : Icons.verified_user_outlined,
|
||||
size: 18,
|
||||
color: seedGreen,
|
||||
),
|
||||
Icon(icon, size: 18, color: strong ? seedGreen : seedMuted),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
known
|
||||
? t.trust.circle
|
||||
: state.certifierCount == 0
|
||||
? t.trust.none
|
||||
: t.trust.count(n: state.certifierCount),
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: known ? seedGreen : seedOnSurface,
|
||||
color: strong ? seedGreen : seedOnSurface,
|
||||
fontSize: 13,
|
||||
fontWeight: known ? FontWeight.w600 : FontWeight.w400,
|
||||
fontWeight: strong ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue