feat(store,doctor): surface the hub's trust + exposure data (0.77.0)
Some checks failed
Security / Security check (push) Failing after 1s

The trust-gate dialog replaces its generic 'no per-entry status yet
(alpha)' note with the hub's classified verification statement —
pinned store key / trusted publishers (green), installs without
signature checking (amber), install would be refused (red), bridge
entry (neutral) — via one shared describeInstallVerification mapping.
Against a pre-0.23 hub the field is empty and the old honest wording
stays (pinned by test).

Information architecture: policy-off is a GLOBAL fact, so it appears
as ONE ChainInlineHelp notice above the store grid instead of a
warning pill on every card (card noise); only 'blocked' — a genuine
per-source anomaly — earns a card pill. Doctor's host services show
the hub-classified network reach per endpoint (local only / private
network / publicly reachable with a protect-it hint / reach unknown).

Verified end-to-end against the live dev hub (guide harness): the
wire field arrives, the store page shows exactly one policy notice
and quiet cards; trust-gate variants captured via the dialog
harness. Suite 123 green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-21 13:14:13 +02:00
parent 87afa4dc05
commit 2f076ccf29
15 changed files with 732 additions and 13 deletions

View file

@ -5,14 +5,16 @@
// quick fix) now routes through this dialog, which shows what
// the hub actually knows before anything is downloaded origin
// store, version, license, maturity, required services plus
// an honest note on when signature verification happens. No
// fake "verified" badge: per-entry signature status is not in
// the store index yet, and the dialog says so instead of
// pretending.
// the per-entry verification statement the hub computes with the
// SAME resolvers the install gate enforces (pinned key / trusted
// publishers / policy off / blocked / federated). Against a
// pre-0.23 hub the field is empty and the dialog keeps its
// generic when-verification-happens note instead of guessing.
import 'package:flutter/material.dart';
import '../data/hub.dart';
import '../data/install_verification.dart';
import '../l10n/app_localizations.dart';
import '../theme/tokens.dart';
@ -184,12 +186,65 @@ class ChainInstallConfirmDialog extends StatelessWidget {
),
),
const SizedBox(height: ChainSpace.xs),
Text(
l.installConfirmSignatureNote,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.4,
),
Builder(
builder: (context) {
final info = describeInstallVerification(
item.installVerification,
l,
);
if (info == null) {
// Pre-0.23 hub: keep the generic note.
return Text(
l.installConfirmSignatureNote,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.4,
),
);
}
final color = switch (info.tone) {
VerificationTone.good => ChainColors.success,
VerificationTone.warning => ChainColors.warning,
VerificationTone.danger => theme.colorScheme.error,
VerificationTone.neutral =>
theme.colorScheme.onSurfaceVariant,
};
final icon = switch (info.tone) {
VerificationTone.good => Icons.verified_outlined,
VerificationTone.warning =>
Icons.warning_amber_outlined,
VerificationTone.danger => Icons.gpp_bad_outlined,
VerificationTone.neutral => Icons.link_outlined,
};
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 6),
Expanded(
child: Text(
info.label,
style: theme.textTheme.bodySmall?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 2),
Text(
info.body,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.4,
),
),
],
);
},
),
],
),