RFC-001 — Error Reporting Framework

Field Value
RFC 001
Title Error Reporting Framework
Status Draft
Date 2026-04-26
Scope Stack-wide — all modules, all platforms, all UI types
Components foundation/reporter (new), engines/sdk/koder_kit (extend), ai/kortex (extend), specs/errors/reporting.kmd (new)

1. Abstract

This RFC defines a unified, privacy-first error reporting framework for the Koder Stack. It introduces four coordinated pieces:

  1. foundation/reporter — a new backend service that ingests, deduplicates, sanitizes, and routes field error reports from all modules.
  2. engines/sdk/koder_kit extension — a KoderErrorReporter service and KoderReportButton widget that every module uses without boilerplate.
  3. ai/kortex integration — an asynchronous AI analysis layer that performs semantic deduplication, cross-module correlation, and ticket enrichment. Connected via event queue; never in the synchronous ingestion path.
  4. specs/errors/reporting.kmd — a normative spec that defines the data model, privacy contract, consent model, retention policy, and access control rules.

The goal is to bridge the gap between what Koder Observability sees (server-side logs and traces) and what users actually experience in the field, while maintaining a strict privacy-first posture by default. Kortex makes the system intelligent without compromising the privacy boundaries — it receives only technical groups (A–D), never user-generated content (Group G).


2. Motivation

The Koder Stack already has:

  • specs/errors/user-facing-messages.kmd — canonical error IDs and humanized messages.
  • behaviors.kmd §2 — structured telemetry (logs, traces, metrics) emitted by apps and services.
  • observe/ — the observability stack that consumes server-side telemetry.

What is missing:

Gap Impact
No path from a user-visible error back to the engineering team Bugs that affect real users are invisible unless users know to file a report externally
Observability covers server-side only Client-side crashes and errors leave no trace unless explicitly reported
No cross-stack error correlation The same bug in engines/sdk/koder_kit affects Dek, Store, and Kmail; today they appear as three unrelated issues
No user-initiated report mechanism Users have no in-app way to signal a problem with context attached

This framework closes all four gaps with a single, coherent architecture.


3. Architecture

3.1 — Full system diagram

┌──────────────────────────────────────────────────────────────┐
│                       Koder Modules                          │
│     (Dek, Store, Kmail, kdb, koder-jet, koder-id, …)        │
│                                                              │
│   engines/sdk/koder_kit: KoderErrorReporter                          │
│   ┌────────────────────┐   ┌────────────────────────────┐    │
│   │  Auto report       │   │  Manual report              │    │
│   │  (opt-in)          │   │  KoderReportButton          │    │
│   │                    │   │  Groups A–D + G (opt-in)    │    │
│   │  offline queue     │   │                            │    │
│   │  (SQLite, 50 max,  │   │                            │    │
│   │   TTL 7d)          │   │                            │    │
│   └────────┬───────────┘   └────────────┬───────────────┘    │
└────────────┼────────────────────────────┼────────────────────┘
             │ HTTPS                      │ HTTPS
             ▼                            ▼
┌──────────────────────────────────────────────────────────────┐
│                   foundation/reporter                        │
│                                                              │
│  1. Schema validation → 400 if invalid                       │
│  2. Rate limiting (10/min auth · 2/min anon) → 429          │
│  3. Hash dedup: fingerprint already seen? → 202, stop        │
│  4. PII scrub (2nd layer)                                    │
│  5. GeoIP: IP → country → discard IP                        │
│  6. Emit "report.new" to event queue  ←─── async from here  │
│  7. → 202 Accepted to SDK (immediate)                        │
│  8. Write to audit log                                       │
└──────────────────────────┬───────────────────────────────────┘
                           │
                    event queue
                    "report.new"
                    (Groups A–D only;
                     Group G excluded)
                           │
                           ▼
┌──────────────────────────────────────────────────────────────┐
│                    ai/kortex consumer                        │
│                                                              │
│  ┌─ Phase 1 ────────────────────────────────────────────┐   │
│  │  Semantic dedup                                        │   │
│  │  • embed stack trace → vector                         │   │
│  │  • compare vs. fingerprint index                      │   │
│  │  • similarity > threshold?                            │   │
│  │      YES → "report.grouped" (existing ticket comment) │   │
│  │      NO  → continue                                   │   │
│  │                                                        │   │
│  │  Stack analysis                                        │   │
│  │  • identify probable component (sdk vs. app layer)    │   │
│  │  • classify severity justification                    │   │
│  │  • match against historical issue embeddings          │   │
│  │  → "report.enriched"                                  │   │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌─ Phase 2 (+ Flow API access) ────────────────────────┐   │
│  │  • correlate with git log / blame                     │   │
│  │  • cite probable commit + author                      │   │
│  │  • match manual reports (Group G) with known issues   │   │
│  │    → auto-reply to user with workaround if found      │   │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌─ Phase 3 (sliding window over observe/) ─────────────┐   │
│  │  • cross-module pattern detection                     │   │
│  │  • "5 modules, auth errors, 10 min after koder_kit   │   │
│  │    deploy" → automatic incident                       │   │
│  └────────────────────────────────────────────────────────┘  │
└──────────┬───────────────────────────┬───────────────────────┘
           │ "report.enriched"         │ "report.grouped"
           │ "report.incident"         │
           ▼                           ▼
┌──────────────────────┐   ┌───────────────────────────────┐
│      backlog         │   │       existing ticket         │
│  (new enriched       │   │  (comment: new occurrence,    │
│   ticket, see §14)   │   │   cross-module link)          │
└──────────────────────┘   └───────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────────────┐
│                  Koder Observability                         │
│  • crash rate per module / version / platform                │
│  • fingerprint trend charts (confirm fix worked)             │
│  • alerts: new crash in prod release                         │
│  • Phase 3: anomaly detection on event stream                │
└──────────────────────────────────────────────────────────────┘

3.2 — Why event-driven and not synchronous

Kortex must never be in the synchronous path between the SDK and the 202 response. Two reasons:

  1. Latency: AI inference latency is variable. Under load it can reach seconds. The SDK timeout for report submission should be short (5s) to not degrade app UX. If Kortex were synchronous, one slow inference would cascade into SDK timeouts.
  1. Fault isolation: If Kortex is down or degraded, reports must still be accepted and stored. The event queue buffers events and Kortex processes them when it recovers — zero data loss.

The 202 response to the SDK means "received and stored". Enrichment happens in background, asynchronously.

3.3 — Component responsibilities

foundation/reporter (Go backend, new)

  • Receives reports over HTTPS; responds 202 immediately after validation and hash dedup.
  • Runs PII scrubber as second defense layer (SDK is first).
  • Computes hash fingerprint: sha256(error_id + app_slug + app_version + top_stack_frame).
  • Publishes "report.new" events (Groups A–D only) to the event queue.
  • Maintains audit log of every payload access by Koder engineers.
  • Group G (manual report fields) is stored encrypted, never forwarded to the queue or Kortex.

engines/sdk/koder_kit extension

  • Provides KoderErrorReporter (Dart) with Go and JS equivalents.
  • Applies PII scrubbing before serialization (first layer).
  • Buffers reports offline in a local SQLite queue (max 50 entries, TTL 7 days, priority: crash > error > warning).
  • Provides KoderReportButton widget for the Settings UI.

Event queue

  • Decouples reporter from Kortex; absorbs Kortex downtime without data loss.
  • Carries only Groups A–D — Group G never enters the queue.
  • Implementation: NATS JetStream (already in Koder infra) or equivalent.

ai/kortex consumer (new consumer role on existing Kortex)

  • Subscribes to "report.new" events.
  • Phase 1: semantic dedup + stack analysis → emits "report.enriched" or "report.grouped".
  • Phase 2: adds Flow API access for commit correlation and known-issue matching.
  • Phase 3: sliding window analysis over observe/ event stream for systemic pattern detection.
  • Has NO access to Group G. Has NO access to the reporter's raw payload store.

Backlog consumer

  • Subscribes to "report.enriched" events → creates enriched tickets (see §14).
  • Subscribes to "report.grouped" events → posts comment on existing ticket.
  • Subscribes to "report.incident" events (Phase 3) → opens high-severity incident ticket.

specs/errors/reporting.kmd (normative spec)

  • Single source of truth for MUST/SHOULD rules across all modules.
  • /k-housekeep audits every module against this spec.

4. Data Model

Reports are structured into groups. Each group has an explicit capture policy.

Group A — Report Identity

Always captured. No PII.

Field Type Example
report_id uuid-v4 a3f2... — generated on client, prevents server-side duplicates
reported_at ISO 8601 UTC 2026-04-26T15:30:00Z
occurred_at ISO 8601 UTC May differ from reported_at if queued offline
report_type enum user_report / auto_crash / auto_error / auto_anr

Group B — Module Identity

Always captured. No PII.

Field Type Example
app_slug string dek, store, kmail
app_version semver 1.9.30
build_number int 47
distribution_channel enum play_store, deb, appimage, koder_store, sideload
build_type enum release / debug

debug build reports are accepted but tagged separately and excluded from production dashboards by default.

Group C — Platform and Environment

Always captured. No PII.

Field Type Example Notes
platform enum android, linux, ios, macos, windows, web
os_version string Android 15 (API 35) Non-PII; essential for platform-specific bug triage
device_class enum phone, tablet, desktop, tv, server
device_model_hash string sha256("SM-A055M")[:16] Hash only — never the raw model string
architecture enum arm64, x86_64, arm32
locale BCP 47 pt-BR
timezone_offset string UTC-3 Offset only — no city or region name

Group D — Error Context

Always captured. No PII.

Field Type Example Notes
error_id string DEK-REC-001-0042 Canonical ID per specs/errors/user-facing-messages.kmd
error_category string recording, sync, auth
severity enum crash, error, warning
stack_trace string sanitized Absolute paths replaced with relative; see §5.2
error_message_raw string IOException: ... Technical message, never user content
is_fatal bool true Whether the error caused the app/service to terminate
recurrence_in_session int 3 How many times this error occurred in the current session
fingerprint string sha256(...)[:32] Computed by SDK; used for dedup

Group E — Navigation Breadcrumbs

Opt-in. Default: OFF.

Field Type Example Notes
current_route string /recordings/detail Route template only — never path parameters
breadcrumbs string[] ["/home", "/recordings"] Last N routes — no query params, no IDs
last_action string tap:play_button Action type and target name — never content
app_uptime_seconds int 142 Useful for detecting memory leak patterns

Route parameters and query strings MUST be stripped by the SDK before including breadcrumbs. Example: /recordings/detail/abc123?user=joao becomes /recordings/detail.

Group F — Device State

Opt-in. Default: OFF.

Field Type Example Notes
network_state enum wifi, cellular, offline
memory_available_mb int 312 Available RAM at time of error
storage_available_mb int 4200
battery_level int 0–100 78 Mobile only
battery_charging bool false Mobile only
app_in_foreground bool true Background errors have distinct patterns

Group G — Manual Report (user-initiated only)

Always opt-in, field by field. Never automatic.

Field Notes
user_description Free text typed by the user. Optional. Displayed verbatim to Koder engineers.
user_contact_email Only if user explicitly fills in a "notify me when fixed" field. Stored encrypted, used solely for that notification, then deleted.
screenshot_included Boolean. The SDK prompts a separate, explicit consent dialog before attaching.
screenshot_data JPEG, max 1 MB. Only included if screenshot_included = true after explicit consent.

Screenshot consent dialog text (en-US): "Attach a screenshot? It may contain personal information visible on your screen." with "Attach" / "Skip" options. No pre-selected default.

Backend services (kdb, koder-jet, koder-id, etc.)

Groups A, B, C (minus mobile-specific fields), and D apply. Groups E and F are replaced by:

Field Type Example
request_id uuid Trace ID of the request that failed
operation string PUT /api/v1/recordings
http_status int 500
upstream_dependency string kdb, s3, koder-id
retry_count int 2
response_time_ms int 4320

5. Privacy Contract

5.1 — What is NEVER captured

The following are unconditionally prohibited, regardless of user consent or configuration. This is enforced in the SDK (first layer) and in the foundation/reporter PII scrubber (second layer):

Category Examples
User identity data Names, email addresses, phone numbers, national IDs (CPF, RG)
User-generated content Audio recordings, video, documents, messages, notes, transcriptions
Input events Keyboard events, key sequences, clipboard contents
Session replay Screen recordings, video of UI interactions, gesture sequences
Media device access Microphone, camera — the reporter SDK has no access to these APIs
Contact and calendar data Address book, calendar entries, call history
Real device identifiers IMEI, Android ID, IDFA, serial number — use device_model_hash only
Raw IP addresses Accepted server-side for rate limiting, then discarded; only country is retained
Absolute file paths containing usernames /home/joao/... → sanitized to ~/.../
Tokens and credentials Auth tokens, API keys, passwords — scrubbed by regex before transmission

5.2 — Stack trace sanitization rules

Applied by the SDK before serialization:

  1. Replace absolute paths with relative: /home/username/dev/koder/...koder/...
  2. Strip query parameters from any URL in the trace.
  3. Redact values matching patterns: email addresses, UUIDs longer than 16 chars in user-visible fields, phone numbers.
  4. Do not include local variable values unless the module explicitly opts into variable capture (not enabled by default; requires a separate RFC).

5.3 — IP handling at the server

foundation/reporter receives the client IP for rate limiting and abuse prevention. After resolving country via GeoIP, the IP is discarded immediately — it is never stored, logged, or included in the report record.


6.1 — Default by module type

Module type Auto-report default Breadcrumbs/state default User toggle location
Mobile app (Android, iOS) OFF OFF Settings → Privacy → Error Reporting
Desktop app (Linux, macOS, Windows) OFF OFF Settings → Privacy → Error Reporting
TV app (TizenOS, WebOS) OFF OFF Settings → Privacy → Error Reporting
Web app (SaaS, admin, PWA) OFF OFF Settings → Privacy → Error Reporting
Backend service (kdb, koder-jet, etc.) OFF N/A Env var / koder.toml — operator decision, not user
SDK (koder_kit, enginessdkgo, enginessdkjs) N/A N/A Inherits from the host module

The "absent preference = OFF" rule applies universally for user-facing modules:

// Dart / Flutter
final prefs = await SharedPreferences.getInstance();
final enabled = prefs.getBool('error_reporting') ?? false;  // absent == OFF
// Go services
enabled := os.Getenv("KODER_ERROR_REPORTING") == "true"  // absent == OFF
// Web / TV
const enabled = localStorage.getItem('error_reporting') === 'true';  // absent == OFF

Note: This is the opposite of the auto_update toggle (behaviors.kmd §4), which defaults to ON. Error reporting defaults to OFF because it involves transmitting runtime data from the user's device, even if no PII is included.

6.2 — Granular opt-in for Groups E and F

Even when auto-report is enabled, Groups E (breadcrumbs) and F (device state) require separate toggles:

Settings → Privacy → Error Reporting
  [●] Send error reports automatically
      [○] Include navigation breadcrumbs       (default: OFF)
      [○] Include device state (memory, battery) (default: OFF)

Child toggles are only visible when the parent toggle is ON.

6.3 — Manual reports (Group G)

The "Report a problem" button in Settings is always available, regardless of the auto-report toggle. Sending a manual report is a one-time explicit action; it does not change the auto-report setting.

Screenshot attachment requires a second, separate tap on "Attach screenshot" within the report dialog.


7. API Contract — foundation/reporter

Base URL: https://reporter.koder.dev/api/v1

Authentication: Bearer token from Koder ID (same OIDC flow as other services). Anonymous reports are accepted for crash reports only, with stricter rate limiting.

POST /reports

Submit a single error report.

Request

{
  "report_id": "uuid-v4",
  "reported_at": "2026-04-26T15:30:00Z",
  "occurred_at": "2026-04-26T15:29:58Z",
  "report_type": "auto_crash",
  "module": {
    "app_slug": "dek",
    "app_version": "1.9.30",
    "build_number": 47,
    "distribution_channel": "play_store",
    "build_type": "release"
  },
  "platform": {
    "platform": "android",
    "os_version": "Android 15 (API 35)",
    "device_class": "phone",
    "device_model_hash": "a3f2b1c9d0e4",
    "architecture": "arm64",
    "locale": "pt-BR",
    "timezone_offset": "UTC-3"
  },
  "error": {
    "error_id": "DEK-REC-001-0042",
    "error_category": "recording",
    "severity": "crash",
    "stack_trace": "...",
    "error_message_raw": "IOException: ...",
    "is_fatal": true,
    "recurrence_in_session": 1,
    "fingerprint": "sha256-truncated"
  },
  "breadcrumbs": null,
  "device_state": null,
  "user_report": null
}

Responses

Code Meaning
202 Accepted Report queued for processing
400 Bad Request Schema validation failed
409 Conflict report_id already received (client can ignore)
429 Too Many Requests Rate limit exceeded; Retry-After header included

POST /reports/batch

Submit up to 50 reports in one request. Used when reports were queued offline.

GET /reports/{fingerprint}/status

Returns dedup status for a given fingerprint (for the SDK to avoid re-submitting known issues).

{ "fingerprint": "...", "known": true, "first_seen": "2026-04-20T10:00:00Z", "occurrence_count": 142 }

8. SDK Integration — engines/sdk/koder_kit

8.1 — Configuration (Dart / Flutter)

KoderApp(
  errorReporting: KoderErrorReportingConfig(
    autoReportEnabled: prefs.getBool('error_reporting') ?? false,
    includeBreadcrumbs: prefs.getBool('error_reporting_breadcrumbs') ?? false,
    includeDeviceState: prefs.getBool('error_reporting_device_state') ?? false,
    reporterEndpoint: 'https://reporter.koder.dev/api/v1',
  ),
  child: MyApp(),
)

8.2 — Manual report widget

KoderReportButton(
  // Placed in Settings → Privacy → Error Reporting
  onSent: (_) => ScaffoldMessenger.of(context)
      .showSnackBar(SnackBar(content: Text('Report sent. Thank you.'))),
)

KoderReportButton internally:

  1. Opens a bottom sheet with an optional description field.
  2. Offers a separate "Attach screenshot" button with an explicit consent notice.
  3. Collects Groups A–D (always), G (user-provided fields only).
  4. Submits to foundation/reporter.

8.3 — Automatic capture (Flutter)

// In engines/sdk/koder_kit — wraps FlutterError.onError and PlatformDispatcher.onError
KoderErrorReporter.initialize(config);
// After this, unhandled exceptions and Flutter framework errors are
// automatically captured and submitted when auto-report is enabled.

8.4 — Go services

import "dev.koder/sdk/go/reporter"

rep := reporter.New(reporter.Config{
    AppSlug:    "koder-jet",
    AppVersion: "1.22.0",
    Enabled:    os.Getenv("KODER_ERROR_REPORTING") == "true",
    Endpoint:   "https://reporter.koder.dev/api/v1",
})

// Wrap error at the call site
if err != nil {
    rep.Report(ctx, err, reporter.WithOperation("PUT /vhosts"))
    return err
}

9. Settings UI Requirements

All user-facing modules MUST include the following in Settings:

Location: Settings → Privacy → Error Reporting (or the platform equivalent)

Required elements:

  1. Section title: "Error Reporting" / "Relatório de Erros"
  2. Primary toggle: "Send error reports automatically" / "Enviar relatórios de erro automaticamente"
    • Default: OFF
    • Subtitle: "Sends technical information when the app encounters an error. Never includes your recordings, files, or personal data."
  3. Child toggle (visible only when primary is ON): "Include navigation history"
    • Default: OFF
    • Subtitle: "Adds the sequence of screens visited before the error."
  4. Child toggle (visible only when primary is ON): "Include device state"
    • Default: OFF
    • Subtitle: "Adds memory, storage, and battery level at the time of the error."
  5. Button: "Report a problem"
    • Always visible, regardless of toggle state.
    • Opens the manual report flow.
  6. Link: "What data is sent?" → opens in-app page or URL with the full data inventory from this RFC.

Audit requirement: /k-housekeep verifies that every Settings screen contains a widget referencing the error_reporting preference key.


10. Retention Policy

Data type Retention Rationale
Raw report payload (all groups) 90 days Sufficient for triage and fix cycles
Deduplicated fingerprint + occurrence count 2 years Trend analysis across releases
Manual report user_description 1 year, or until ticket closed Needed while bug is open
user_contact_email Until notification sent, then deleted Single-purpose; deleted immediately after use
Screenshot data 90 days, or until ticket closed No longer needed after fix verification
Audit log entries 3 years Compliance and access accountability
Server-received IP (before discard) Not stored Discarded after GeoIP resolution

Data subject access requests (GDPR / LGPD): since reports contain no user identifiers by design, the reporter cannot respond to "delete my data" requests by user identity. The user_contact_email (Group G, optional) is the only linkable field; it is deleted on request.


11. Access Control

Access to report data within Koder follows the principle of least privilege.

Role Can access
Any engineer Aggregated stats, dedup groups, occurrence counts, trend charts
Module owner Full report payload for their own module's reports
Security team All reports, all modules
On-call engineer Full payload for reports triggering active incidents
CEO / non-engineering Aggregated stats only, no raw payloads

Access is mediated by foundation/reporter's internal ACL, backed by Koder ID roles. There is no direct database access to the reports table for any role.


12. Audit Log

Every access to a report payload (not just aggregate stats) is recorded:

{
  "audit_id": "uuid",
  "accessed_at": "2026-04-26T16:00:00Z",
  "accessor_user_id_hash": "sha256(koder_id)[:16]",
  "accessor_role": "module_owner",
  "report_id": "uuid",
  "app_slug": "dek",
  "access_reason": "incident:INC-0042"
}
  • Audit log entries are immutable and append-only.
  • Accessible only to the Security team.
  • Retained for 3 years.
  • Exported to observe/ for anomaly detection (e.g., bulk access patterns).

13. Integration with Koder Observability

Two events flow from the pipeline to observe/, at different stages:

Phase 1 — After reporter ingestion (report.received): emitted immediately after the reporter accepts the report. Contains only factual fields — no Kortex analysis yet.

{
  "service": "reporter",
  "event": "report.received",
  "app_slug": "dek",
  "app_version": "1.9.30",
  "error_id": "DEK-REC-001-0042",
  "severity": "crash",
  "fingerprint": "...",
  "is_new_fingerprint": true,
  "occurrence_count": 1,
  "platform": "android",
  "distribution_channel": "play_store"
}

Phase 1 — After Kortex analysis (report.analyzed): emitted by the Kortex consumer after processing. Contains the enrichment results.

{
  "service": "kortex",
  "event": "report.analyzed",
  "report_id": "uuid",
  "fingerprint": "...",
  "kortex_component": "engines/sdk/koder_kit",
  "kortex_severity_justification": "crash in shared audio session setup — affects all modules using KoderAudio",
  "kortex_similar_issues": ["#087", "#061"],
  "is_semantic_duplicate": false,
  "grouped_with_fingerprint": null
}

Phase 3 — Kortex pattern detection (report.incident): emitted when Kortex detects a systemic pattern across modules or across a time window.

{
  "service": "kortex",
  "event": "report.incident",
  "pattern": "auth_failure_spike",
  "affected_modules": ["dek", "store", "kmail"],
  "probable_cause": "koder_kit v0.5.1 deploy 10 min ago",
  "occurrence_count": 47,
  "window_minutes": 10,
  "suggested_action": "rollback koder_kit to v0.5.0"
}

These feed:

  • Dashboards: crash rate per moduleversionplatform; Kortex component breakdown.
  • Alerts: new crash fingerprint in production; crash rate above threshold; systemic incident detected.
  • Trend charts: occurrence count over time per fingerprint — used to confirm a fix worked in field (not just in CI).
  • Regression detection: if a fingerprint marked resolved reappears in a newer version, an alert fires immediately.

14. Automatic Backlog Integration

Ticket creation happens in two steps, separated by the async Kortex analysis:

Step 1 — reporter receives new fingerprint: creates a minimal ticket immediately, so engineers can see the crash even before Kortex finishes.

Step 2 — Kortex emits report.enriched: the backlog consumer updates the ticket with the enrichment. If Kortex identifies a semantic duplicate, the ticket is closed and a comment is added to the existing one.

backlog/pending/NNN-crash-DEK-REC-001-0042.md

Enriched ticket template (post-Kortex)

# Crash: DEK-REC-001-0042

**Severity**: crash  
**First seen**: 2026-04-26 15:29 UTC  
**Platform**: Android 15 / arm64  
**Version**: 1.9.30 (play_store)  
**Fingerprint**: abc123...  
**Occurrences**: 1 (updated automatically)

## Kortex Analysis

**Probable component**: `engines/sdk/koder_kit` — audio session setup  
**Severity justification**: crash in shared component; affects all modules using KoderAudio  
**Similar past issues**: #087 (fixed v0.4.2), #061 (fixed v0.3.8) — check if regression  

<!-- Phase 2 only, when Flow API is connected: -->
**Probable commit**: `a3f2b1c` — "refactor: KoderAudio session lifecycle" (2026-04-25)  
**Suggested area**: `engines/sdk/koder_kit/lib/src/audio/audio_session.dart`

## Stack trace
...

## Cross-module occurrences
<!-- populated by Kortex when same semantic fingerprint appears in other modules -->
| Module | Version | Count |
|---|---|---|
| dek | 1.9.30 | 1 |

## Occurrence history
| Date | Count | Version |
|---|---|---|
| 2026-04-26 | 1 | 1.9.30 |

---
Auto-opened by foundation/reporter. Enriched by ai/kortex.

Escalation thresholds

When occurrence count crosses thresholds (configurable per module in koder.toml, global default shown):

Threshold Action
10 Comment on ticket: "10 occurrences reached"
100 Raise severity to high; notify module owner
1000 Raise severity to critical; notify on-call
Resolved fingerprint reappears Reopen ticket automatically; tag as regression

Feedback loop: fix confirmation

When an engineer closes a ticket:

  1. Backlog consumer sends POST /reports/{fingerprint}/resolve to the reporter with the fix version.
  2. Reporter marks the fingerprint as resolved_in: "1.9.31".
  3. If the same fingerprint appears in version ≥ 1.9.31, the reporter emits report.regression → backlog reopens the ticket tagged [REGRESSION].

15. Implementation Phases

Phase 1 — Foundation + Kortex semantic dedup

Prerequisites for all subsequent phases. Kortex enters here, not later, because dedup quality determines the entire backlog structure.

  • [ ] specs/errors/reporting.kmd — normative spec (derived from this RFC)
  • [ ] foundation/reporter — ingest, hash dedup, PII scrub, event queue publish
  • [ ] Event queue — NATS JetStream topic errors.reports.new
  • [ ] engines/sdk/koder_kitKoderErrorReporter (auto capture + offline queue), KoderReportButton (manual)
  • [ ] /k-housekeep audit rule: Settings screen must have error_reporting key
  • [ ] ai/kortex consumer — semantic dedup (vector embedding + similarity index) + stack analysis
  • [ ] Backlog consumer — subscribes to report.enriched and report.grouped
  • [ ] observe/report.received and report.analyzed event ingestion; basic dashboards
  • [ ] Feedback loop — POST /reports/{fingerprint}/resolve endpoint + regression detection

Kortex data access in this phase: Groups A–D only. No Flow API. No Group G. No observe/ stream.

Phase 2 — Module rollout + Kortex commit correlation

  • [ ] Dek — wire KoderErrorReporter + Settings UI
  • [ ] Koder Hub — wire KoderErrorReporter + Settings UI
  • [ ] Kmail — wire KoderErrorReporter + Settings UI
  • [ ] koder-jet — Go reporter SDK + env var config
  • [ ] kdb — Go reporter SDK + env var config
  • [ ] ai/kortex + Koder Flow API — commit/blame correlation in enriched tickets
  • [ ] Known-issue matching for manual reports (Group G) — auto-reply with workaround when match found
  • [ ] Escalation thresholds (10 / 100 / 1000 occurrences) — severity promotion + notifications
  • [ ] Screenshot diff on regression detection

Kortex data access in this phase: Groups A–D + Koder Flow API (read-only). Group G is matched by the reporter (not forwarded to Kortex); Kortex receives only the error_id and fingerprint for matching.

Phase 3 — Systemic pattern detection (post-stabilization)

Roll out only after 6+ months of production data and stable Phase 1+2.

  • [ ] ai/kortex sliding window consumer on observe/ event stream
  • [ ] Cross-module pattern detection → report.incident events
  • [ ] Automatic incident ticket creation with rollback suggestion
  • [ ] observe/ alert rules for report.incident events
  • [ ] Full crash rate dashboards with Kortex component attribution

Kortex data access in this phase: Groups A–D + observe/ aggregated event stream (no raw payloads). Still no Group G.


16. Open Questions

Previously open questions from v1 — resolved:

Question Resolution
Offline queue Yes. SQLite in the SDK, max 50 entries, TTL 7 days, priority crash > error > warning. Defined in §3.3.
Anonymous reports Yes — crash reports accepted without Koder ID token, with stricter rate limiting (2min vs 10min). Captures crashes before auth flow completes.
Koder ID cross-reference No. Reporter stays isolated from identity data. user_contact_email stored encrypted in reporter only; Kortex never sees it.
Report preview Yes. KoderReportButton shows an expandable "Show what will be sent" section before submission.
Threshold configuration Global default (101001000) with per-module override in koder.toml[error_reporting].

Still open:

  1. Kortex similarity threshold: What cosine similarity value triggers "semantic duplicate"? Too low → false groupings hide distinct bugs. Too high → no grouping benefit. Recommendation: start at 0.92, tune after 30 days of data. Needs a feedback mechanism (engineer can "ungroup" a false positive).
  1. Kortex embedding model: Which embedding model for stack traces? Stack traces have a distinct structure (frames, line numbers) that general-purpose models may not handle optimally. Recommendation: evaluate code-embedding models vs. fine-tuning on historical Koder stack traces.
  1. Queue backpressure: If Kortex is slow and the queue grows, should the reporter apply backpressure to SDK submissions or let the queue grow unbounded? Recommendation: cap queue at 100k events; oldest events dropped (not reports — they're already stored) with an alert when cap is reached.
  1. Group G and known-issue matching in Phase 2: The reporter (not Kortex) compares the manual report's error_id and fingerprint against the known-issue index. But who maintains the known-issue index? Recommendation: Kortex populates it as a side effect of Phase 1 analysis; reporter queries it read-only.
  1. Multi-tenant isolation: If the Koder Stack is ever offered as a platform to external customers, each tenant's reports must be isolated. Recommendation: design the reporter's data model with tenant_id from the start even if all reports are Koder's own in Phase 1.

17. Relation to Existing Specs

Existing spec Relation
specs/errors/user-facing-messages.kmd Provides the error_id format used in Group D. No changes needed.
behaviors.kmd §2 (Telemetry) Complementary — §2 covers server-side structured logs; this RFC covers field error reports.
behaviors.kmd §2.4 (no PII in logs) Extended here to apply to error reports. §5.1 of this RFC is the error-reporting-specific version.
policies/sdk-first.kmd This RFC follows sdk-first: the SDK (koder_kit) is the implementation vehicle; no module implements error reporting locally.
ai/kortex architecture Kortex is consumed as an existing service; this RFC adds a new consumer role. No changes to Kortex's core architecture. The event queue contract (§3.3) is the integration boundary.

18. Kortex Privacy Boundaries

This section is normative for any engineer extending or modifying the Kortex consumer role.

What Kortex receives

Data Received Rationale
Groups A–D (technical report data) Yes Non-PII; required for analysis
Group E (breadcrumbs, if user enabled) Yes Route names only, no content
Group F (device state, if user enabled) Yes Environmental metrics only
Group G (user description, email, screenshot) No User-generated content; stays in reporter only
Raw IP address No Discarded by reporter before queue publish
User identity of reporter No Reports are anonymous from Kortex's perspective

What Kortex may store

  • Vector embeddings of stack traces — for the fingerprint similarity index.
  • Analysis results (kortex_component, kortex_severity_justification, kortex_similar_issues) — emitted as events, not stored long-term by Kortex.
  • The known-issue index (fingerprint → issue mapping) — maintained as a side effect of Phase 1 analysis.

Kortex may not store raw report payloads. It receives events from the queue, processes them, emits result events, and discards the input.

Audit

Every Kortex inference on a report event is logged in the audit log with:

  • event_id (from the queue)
  • kortex_model_version
  • inference_duration_ms
  • output_event_type (report.enriched / report.grouped / report.incident)

This allows retroactive review if a model version produces incorrect groupings or analyses.


This RFC becomes normative once specs/errors/reporting.kmd is updated from it and Phase 1 implementation is complete. Until then, it is a design reference.