| D-M4-31-0 |
2026-10-19 |
Base int/m1-wave46 at 9c05469, branch feat/M4-31-client-metrics in its own worktree; not merged locally and not pushed, the PR lane lands it. The contract the brief needs (submitClientMetrics, client_metrics.submit, client.metrics.flush_interval, R-CL-10, D-W-100, D-W-114, D-W-116) is on the base, so nothing is ported. The base names the chapters in German (15-client-architektur.md, adr/0057-client-telemetrie-ueber-einen-server-endpunkt.md); the rulings stand in a section of the ticket because the base has no section 10. Differences to the brief's names, following the merged code: the server use case lives in internal/stats/app and internal/stats/domain with the adapters httpapi and otelmetrics (02 4 layers), the build define of the crash target is CRASH_DSN (with CRASH_ENVIRONMENT, next to the upper case BRANDING_* defines), and the app wiring is lib/telemetry/telemetry_providers.dart. |
Instructions of the launcher; merged names win over the brief (D-W-100, D-W-114). |
| D-M4-31-1 |
2026-10-19 |
Histogram path: the app histograms reach the Collector as own cumulative OTLP histogram data points. metrics.AggregatedSet (a sdkmetric.Producer on the OTLP metric reader) keeps one histogram per name and attribute set with the bounds of signals.md 4.7 and adds the client's bucket counts unchanged; count and bucket counts are exact, the sum is an upper bound (each value at its bucket's upper bound), minimum and maximum are absent. An entry with a bound outside the 4.7 row, a repeated bound or a negative count is discarded; discarded entries count in bothy.client_metrics.discarded by reason (added to signals.md). The app clamps a value above the top bound into the top bucket (le 10). |
ADR 0057 left the path open; one measurement per bucket count would let the SDK re-aggregate and could not keep the counts exact. Brief Design note 3. |
| D-M4-31-2 |
2026-10-19 |
The sender takes the collected entries every client.metrics.flush_interval and when the app quits or signs out, sends them with submitClientMetrics outside the outbox, split at maxItems (200), only while privacy.client_metrics is on (off: the entries are dropped, nothing waits). A network error keeps the newest batch for the next interval and drops older ones; any other error drops the batch; 403 feature_disabled stops the sender until the setting changes. |
Telemetry is not user data and the operation is idempotency-exempt; never more than one batch waits (brief Design note 2, 01 11). |
| D-M4-31-3 |
2026-10-19 |
The crash target comes from the build configuration only (define CRASH_DSN); BuildBranding.hasCrashTarget is true when it is set, replacing the stand-in define BRANDING_CRASH_REPORTS of D-M4-25-4. Sentry starts only with a target and privacy.crash_reports on (default on with a target) and closes when the switch is turned off. Options: no default PII, no screenshots, no view hierarchy, no replay, no user interaction breadcrumbs, no traces; beforeSend and beforeBreadcrumb clear messages, exception values, titles, request data with every header and body, user, extra, non-technical contexts (an allow-list), file paths outside package: and dart: frames and the URL parts after the path, and replace path segments that look like IDs, tokens or file names by :id; the hashed instance ID is the tag instance. |
15 16 allows the build configuration or the instance settings, but no instance setting key exists (a core contract change, out of scope). The canary test found that a context set by the app survived a deny-list, so contexts use an allow-list. |
| D-M4-31-4 |
2026-10-19 |
tools/ci/dart_sdk_denylist.py runs in make licenses over the root pubspec.lock and fails on a package matching an OpenTelemetry, analytics or monitoring pattern; sentry_flutter and its Dart core sentry are exempt by name, because the lock file has no dependency graph. |
ADR 0057 "Confirmation"; brief Design note 6. |
| D-M4-31-5 |
2026-10-19 |
Crash reports start and metrics are sent only after the personal settings of the signed-in person are read from the local database (personalSettingsLoadedProvider); before the login and while the settings load, both are off, and a sign-out closes Sentry. |
The code review reproduced that the defaults applied while the settings loaded started Sentry at every app start, also for a person who had turned crash reports off (01 11, 15 16: the person can turn them off). The stored choice exists only per instance after the login. |
| D-M4-31-6 |
2026-10-19 |
Crash reports cover Dart errors only: enableNativeCrashHandling and enableAutoSessionTracking are off. |
The native handlers of sentry_flutter (a breakpad minidump with stack memory on Windows, the native beforeSend on macOS) and the session pings never pass the Dart scrubber, so they could carry content past beforeSend (15 16, 17 rule 3). Native crash reports need a native scrubber first, which is outside this ticket. |
| D-M4-31-7 |
2026-10-21 |
Port to main: the ticket's own commits onto main after M1-69, without the integration rulings of its base wave. The documents take the ticket's additions in the English chapters of main (spec 15, ADR 0057, signals.md, the stats README); submitClientMetrics moves from later to enabled in api/operations.yaml and the REST layer is generated again. The strict objects are cut into objects.gen.go and objects2.gen.go by the split that M2-07 brought to main (halveObjects); with this operation both halves stay below repo.max_file_kb. The Dart test of the catalogue rows reads only the English kind words of signals.md. |
Only the ticket's own change lands; English documents on main win. |