feat(store,doctor): surface the hub's trust + exposure data (0.77.0)
Some checks failed
Security / Security check (push) Failing after 1s
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:
parent
87afa4dc05
commit
2f076ccf29
15 changed files with 732 additions and 13 deletions
|
|
@ -1159,6 +1159,9 @@ class _ServicesPanel extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const Spacer(),
|
||||
_ServiceExposurePill(
|
||||
exposure: snapshot.services[i].exposure,
|
||||
),
|
||||
for (final tag in snapshot.services[i].tags) ...[
|
||||
ChainPill(label: tag, tone: ChainPillTone.neutral),
|
||||
const SizedBox(width: ChainSpace.xs),
|
||||
|
|
@ -1173,6 +1176,43 @@ class _ServicesPanel extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Network-reach pill per declared service, from the hub's
|
||||
/// endpoint classification (`DeclaredService.exposure`). Public
|
||||
/// endpoints get the warning tone — a host service reachable from
|
||||
/// outside is the one thing an operator should notice here.
|
||||
/// Empty (pre-0.23 hub) renders nothing.
|
||||
class _ServiceExposurePill extends StatelessWidget {
|
||||
final String exposure;
|
||||
|
||||
const _ServiceExposurePill({required this.exposure});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
final (label, tone, hint) = switch (exposure) {
|
||||
'loopback' => (l.svcExposureLoopback, ChainPillTone.neutral, ''),
|
||||
'private' => (l.svcExposurePrivate, ChainPillTone.neutral, ''),
|
||||
'public' => (
|
||||
l.svcExposurePublic,
|
||||
ChainPillTone.warning,
|
||||
l.svcExposurePublicHint,
|
||||
),
|
||||
'unknown' => (
|
||||
l.svcExposureUnknown,
|
||||
ChainPillTone.neutral,
|
||||
l.svcExposureUnknownHint,
|
||||
),
|
||||
_ => ('', ChainPillTone.neutral, ''),
|
||||
};
|
||||
if (label.isEmpty) return const SizedBox.shrink();
|
||||
final pill = ChainPill(label: label, tone: tone);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: ChainSpace.xs),
|
||||
child: hint.isEmpty ? pill : Tooltip(message: hint, child: pill),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UpdateBanner extends StatefulWidget {
|
||||
final UpdateStatus status;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
|||
|
||||
import '../data/error_presentation.dart';
|
||||
import '../data/hub.dart';
|
||||
import '../data/install_verification.dart';
|
||||
import '../data/system_actions.dart';
|
||||
import '../data/today_story_loader.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
|
@ -383,6 +384,23 @@ class _StorePageState extends State<StorePage> {
|
|||
setState(() => _showStudio = v),
|
||||
),
|
||||
),
|
||||
// ONE page-level notice when the hub says
|
||||
// installs run unverified (policy off) —
|
||||
// a global fact, so it appears once here
|
||||
// instead of as a pill on every card. Old
|
||||
// hubs report nothing => nothing shows.
|
||||
if (raw.any(
|
||||
(i) => i.installVerification == 'unverified',
|
||||
))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: ChainSpace.md,
|
||||
),
|
||||
child: ChainInlineHelp(
|
||||
icon: Icons.gpp_maybe_outlined,
|
||||
text: l.storePolicyUnverifiedNotice,
|
||||
),
|
||||
),
|
||||
if (_aiThinking ||
|
||||
_aiAnswer != null ||
|
||||
_aiError != null) ...[
|
||||
|
|
@ -2136,6 +2154,7 @@ class _StoreCardState extends State<_StoreCard> {
|
|||
const SizedBox(width: ChainSpace.xs),
|
||||
],
|
||||
_ProvenancePill(item: item),
|
||||
_VerificationPill(item: item),
|
||||
const Spacer(),
|
||||
if (_hasUpdate)
|
||||
FilledButton.icon(
|
||||
|
|
@ -3809,6 +3828,39 @@ class _ProvenancePill extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Signature-verification warning pill. Same philosophy as the
|
||||
/// provenance pill: the GOOD path (signature checked at install)
|
||||
/// stays quiet, and so does the policy-off case — that one is a
|
||||
/// GLOBAL property and lives as ONE notice above the grid, not as
|
||||
/// a repeated badge on every card. Only "blocked" (this source
|
||||
/// has no applicable key material although the policy demands
|
||||
/// signatures) is a genuine per-source anomaly worth a badge.
|
||||
class _VerificationPill extends StatelessWidget {
|
||||
final StoreItem item;
|
||||
|
||||
const _VerificationPill({required this.item});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
if (item.installVerification != 'blocked') {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final info = describeInstallVerification(item.installVerification, l)!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: ChainSpace.xs),
|
||||
child: Tooltip(
|
||||
message: info.body,
|
||||
child: ChainPill(
|
||||
label: l.verifPillBlocked,
|
||||
tone: ChainPillTone.danger,
|
||||
icon: Icons.gpp_bad_outlined,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Store-side version picker. Same shape as the module-sheet
|
||||
/// version picker but lives here so the Store page doesn't take
|
||||
/// a dependency on the module-sheet's private widgets. The
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue