fix(#501): apply Gemini review, de-duplicate suppression logging

Three findings reviewed, two applied, one rejected on evidence.

APPLIED. Suppression logging is de-duplicated. Device-info arrives on
every connect, so a flapping radio would have emitted the same
suppression line on each reconnect. It now logs once per distinct
reason, and the memo clears when a gate opens or when per-radio state is
cleared, so the next genuine suppression is still reported. Real, and
exactly the flooding the project forbids.

APPLIED, though not reachable today. The scope reply logged
reply.scope!, which is safe only because parseNotifyScopeReply returns
an ERROR reply when the scope code is unknown, so a non-error reply
always carries a scope. That invariant lives in another file, so it is
now checked at the boundary instead of asserted with a bang across it.
The review rated this High on the theory that an unknown sub-code could
reach the handler; it cannot, because the parser returns null for any
sub-code it does not own and the dispatcher only forwards what it
returned. Defect not reachable, guard still worth having.

REJECTED. The review claimed the matrix reply log is unbounded because
the wire count byte allows up to 255 rows, estimating 7.5 KB per line.
The log is built from ButtonMatrix.assignments, a Map keyed by
ButtonSequence, which has exactly four values, and the parser skips
sequence codes it does not recognise rather than adding them. The map
therefore holds at most four entries and the line is bounded at roughly
160 characters no matter what the device sends. The count byte cannot
inflate it.

Full suite 740 pass, analyze clean, format clean.

Agent: CalmBay (session d14220d9)
fix/580-caplog-partial
Strycher 2 months ago
parent 69da28002f
commit a57ec8f902

@ -309,6 +309,7 @@ class MeshCoreConnector extends ChangeNotifier {
int? _offbandCaps2;
bool? _femLnaEnabled;
ButtonMatrix? _buttonMatrix;
String? _lastDeviceUiSuppression;
DeviceNotifyScope? _deviceNotifyScope;
String? _deviceUiError;
int _pathHashByteWidth = 1;
@ -686,23 +687,36 @@ class MeshCoreConnector extends ChangeNotifier {
/// ambiguous, so the suppression says which gate closed and why. (#501)
bool _deviceUiGateOpen(String what, {required bool advertised}) {
if (!deviceUiCommandLanded) {
_appDebugLogService?.info(
'$what suppressed: 0xC5 command support is compiled off',
tag: 'DeviceUI',
);
_logDeviceUiSuppression(what, '0xC5 command support is compiled off');
return false;
}
if (!advertised) {
_appDebugLogService?.info(
'$what suppressed: radio does not advertise it '
_logDeviceUiSuppression(
what,
'radio does not advertise it '
'(caps2=${_offbandCaps2 == null ? 'absent' : '0x${_offbandCaps2!.toRadixString(16).padLeft(2, '0')}'})',
tag: 'DeviceUI',
);
return false;
}
// A gate that opens clears the memo, so the next genuine suppression is
// reported rather than swallowed as a repeat.
_lastDeviceUiSuppression = null;
return true;
}
/// Log a suppressed request ONCE per distinct reason.
///
/// Device-info arrives on every connect, so a flapping radio would otherwise
/// emit the same suppression line on every reconnect. Diagnostics must not
/// become the outage (SAFELANE §11 rule 10), and a repeated identical line
/// carries no information the first one did not.
void _logDeviceUiSuppression(String what, String reason) {
final memo = '$what|$reason';
if (_lastDeviceUiSuppression == memo) return;
_lastDeviceUiSuppression = memo;
_appDebugLogService?.info('$what suppressed: $reason', tag: 'DeviceUI');
}
Future<void> requestButtonMatrix() async {
if (!_deviceUiGateOpen('matrix GET', advertised: supportsButtonMatrix)) {
return;
@ -766,6 +780,7 @@ class MeshCoreConnector extends ChangeNotifier {
/// reconnect can show one radio's notification scope as another's.
void _clearDeviceUiState() {
_buttonMatrix = null;
_lastDeviceUiSuppression = null;
_deviceNotifyScope = null;
_deviceUiError = null;
}
@ -839,13 +854,26 @@ class MeshCoreConnector extends ChangeNotifier {
tag: 'DeviceUI',
);
} else {
_appDebugLogService?.info(
'scope reply: ${reply.scope!.label} '
'(sub=0x${reply.sub.toRadixString(16).padLeft(2, '0')}, '
'${reply.sub == offbandUiScopeSet ? 'write confirmed' : 'read'})',
tag: 'DeviceUI',
);
_deviceNotifyScope = reply.scope;
final scope = reply.scope;
if (scope == null) {
// Not reachable today: parseNotifyScopeReply returns an error reply
// when the scope code is unknown, so a non-error reply always carries
// one. That invariant lives in another file, so it is checked here
// rather than asserted with a bang across the boundary.
_appDebugLogService?.warn(
'scope reply with no scope (sub=0x'
'${reply.sub.toRadixString(16).padLeft(2, '0')})',
tag: 'DeviceUI',
);
} else {
_appDebugLogService?.info(
'scope reply: ${scope.label} '
'(sub=0x${reply.sub.toRadixString(16).padLeft(2, '0')}, '
'${reply.sub == offbandUiScopeSet ? 'write confirmed' : 'read'})',
tag: 'DeviceUI',
);
_deviceNotifyScope = scope;
}
}
notifyListeners();
}

Loading…
Cancel
Save

Powered by TurnKey Linux.