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:
foundation/reporter— a new backend service that ingests, deduplicates, sanitizes, and routes field error reports from all modules.engines/sdk/koder_kitextension — aKoderErrorReporterservice andKoderReportButtonwidget that every module uses without boilerplate.ai/kortexintegration — 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.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:
- 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.
- 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
KoderReportButtonwidget 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-housekeepaudits 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:
- Replace absolute paths with relative:
/home/username/dev/koder/...→koder/... - Strip query parameters from any URL in the trace.
- Redact values matching patterns: email addresses, UUIDs longer than 16 chars in user-visible fields, phone numbers.
- 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. Consent Model and Defaults
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 == OFFNote: 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:
- Opens a bottom sheet with an optional description field.
- Offers a separate "Attach screenshot" button with an explicit consent notice.
- Collects Groups A–D (always), G (user-provided fields only).
- 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:
- Section title: "Error Reporting" / "Relatório de Erros"
- 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."
- Child toggle (visible only when primary is ON): "Include navigation history"
- Default: OFF
- Subtitle: "Adds the sequence of screens visited before the error."
- 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."
- Button: "Report a problem"
- Always visible, regardless of toggle state.
- Opens the manual report flow.
- 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
resolvedreappears 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.mdEnriched 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:
- Backlog consumer sends
POST /reports/{fingerprint}/resolveto the reporter with the fix version. - Reporter marks the fingerprint as
resolved_in: "1.9.31". - 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_kit—KoderErrorReporter(auto capture + offline queue),KoderReportButton(manual) - [ ]
/k-housekeepaudit rule: Settings screen must haveerror_reportingkey - [ ]
ai/kortexconsumer — semantic dedup (vector embedding + similarity index) + stack analysis - [ ] Backlog consumer — subscribes to
report.enrichedandreport.grouped - [ ]
observe/—report.receivedandreport.analyzedevent ingestion; basic dashboards - [ ] Feedback loop —
POST /reports/{fingerprint}/resolveendpoint + 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/kortexsliding window consumer onobserve/event stream - [ ] Cross-module pattern detection →
report.incidentevents - [ ] Automatic incident ticket creation with rollback suggestion
- [ ]
observe/alert rules forreport.incidentevents - [ ] 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:
- 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).
- 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-embeddingmodels vs. fine-tuning on historical Koder stack traces.
- 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.
- Group G and known-issue matching in Phase 2: The reporter (not Kortex) compares the manual report's
error_idandfingerprintagainst 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.
- 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_idfrom 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_versioninference_duration_msoutput_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.