| @@ -0,0 +1,831 @@ | |||||
| { | |||||
| "document_type": "technical_concept", | |||||
| "language": "de", | |||||
| "audience": [ | |||||
| "AI-Entwicklerteam", | |||||
| "Reviewer", | |||||
| "Maintainer" | |||||
| ], | |||||
| "project": { | |||||
| "name": "fm-rds-tx", | |||||
| "goal": "Aus dem bestehenden FM-Stereo/RDS-TX-System ein technisch sauberes, deterministisches, messbares und betriebsfestes Pro-Level-System machen.", | |||||
| "primary_priority": "Technische Perfektion", | |||||
| "secondary_priority": "Sinnvolle Umsetzungsreihenfolge mit maximalem Risikoabbau zuerst" | |||||
| }, | |||||
| "executive_summary": { | |||||
| "current_strengths": [ | |||||
| "Saubere Modultrennung zwischen Generator, DSP, Control, Audio, Backend und Plattform.", | |||||
| "Persistenter DSP-Zustand ist bereits vorhanden, insbesondere im Generator und im FM-Upsampler.", | |||||
| "Live-Updates werden bereits über atomare Snapshots bzw. Pointer modelliert.", | |||||
| "Es existieren bereits viele Unit-Tests sowie spektrale Blackbox-Tests für 19 kHz, 38 kHz und 57 kHz." | |||||
| ], | |||||
| "current_limitations": [ | |||||
| "Der TX-Pfad in internal/app/engine.go ist noch ein einzelner synchroner Generate/Upsample/Write-Loop ohne entkoppelten Echtzeitpuffer.", | |||||
| "Die Runtime-Recovery ist schwach: bei Fehlern wird nur gezählt, geloggt und gewartet; es gibt keinen expliziten Fault-State mit deterministischem Fallback.", | |||||
| "Die Control-Plane in internal/control/control.go ist funktional, aber nicht hart genug: keine Authentisierung, keine Transporthärtung, keine Request-Limits, keine Timeouts, keine Audit-Trails.", | |||||
| "Validation und Runtime-Semantik sind nicht überall deckungsgleich; Beispiel: fm.outputDrive wird in config.Validate() bis 10 akzeptiert, in Engine.UpdateConfig() aber nur bis 3.", | |||||
| "Device-Abstraktion ist brauchbar, aber noch nicht streng genug capability- und kalibrierungsgetrieben.", | |||||
| "Observability ist noch zu schwach für echten Dauerbetrieb und reproduzierbare Fehleranalyse." | |||||
| ], | |||||
| "core_statement": "Der DSP-Kern ist nah an ernsthaft brauchbar. Der Abstand zu Pro-Level liegt primär in Betriebssicherheit, Observability, Hardwarevalidierung und strenger Runtime-Kontrolle." | |||||
| }, | |||||
| "repo_grounding": { | |||||
| "confirmed_code_touchpoints": [ | |||||
| { | |||||
| "path": "internal/app/engine.go", | |||||
| "observation": "TX läuft aktuell in einer einzelnen Goroutine als synchroner Zyklus: GenerateFrame -> optional FMUpsampler.Process -> driver.Write. Kein echter Producer/Consumer-Puffer zwischen Generator und Hardwarewriter." | |||||
| }, | |||||
| { | |||||
| "path": "internal/offline/generator.go", | |||||
| "observation": "Generator hat bereits persistenten Zustand, LiveParams per atomic.Pointer und sinnvolle DSP-Kette inklusive optionalem BS.412-Limiter." | |||||
| }, | |||||
| { | |||||
| "path": "internal/control/control.go", | |||||
| "observation": "HTTP-Control existiert bereits, aber ohne sichtbare Authentisierung, ohne Server-Timeout-Konfiguration und ohne harte API-Grenzen." | |||||
| }, | |||||
| { | |||||
| "path": "internal/config/config.go", | |||||
| "observation": "Validation ist vorhanden, aber semantisch nicht überall konsistent mit Laufzeitregeln." | |||||
| }, | |||||
| { | |||||
| "path": "internal/platform/soapy.go", | |||||
| "observation": "Capabilities und RuntimeStats existieren als Ansatz, reichen aber noch nicht für eine wirklich harte device-aware Steuerung." | |||||
| }, | |||||
| { | |||||
| "path": "internal/audio/stream.go", | |||||
| "observation": "Lock-freier SPSC-Ringbuffer für Live-Audio ist bereits vorhanden und kann als Referenz für deterministische Buffer-Designs dienen." | |||||
| } | |||||
| ], | |||||
| "confirmed_inconsistencies": [ | |||||
| { | |||||
| "id": "CFG-SEM-001", | |||||
| "description": "fm.outputDrive wird in Config.Validate() bis 10 akzeptiert, die Fehlermeldung spricht aber von 0..3, und Engine.UpdateConfig() erzwingt tatsächlich 0..3." | |||||
| }, | |||||
| { | |||||
| "id": "CTL-UX-001", | |||||
| "description": "handleAudioStream() nennt in der Fehlermeldung '--audio-http', im CLI ist dieser Schalter nicht als gleichwertiger offensichtlicher Bedienpfad bestätigt." | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "design_principles": [ | |||||
| "Kein verstecktes Glück: Jeder relevante Echtzeit- oder RF-Pfad muss deterministisch, messbar und reproduzierbar sein.", | |||||
| "Fail-safe statt fail-weird: Bei Unsicherheit oder Überlast lieber definiert muten oder faulten als kaputt weiterzusenden.", | |||||
| "Eine Runtime-Wahrheit: Konfiguration, Live-State und tatsächlich angewandte Hardware-/DSP-Parameter dürfen nicht auseinanderlaufen.", | |||||
| "Hardware ist Wahrheit: IQ-Dateien und Unit-Tests reichen nicht; es braucht Hardware-in-the-loop und externe Decoder-/Messvalidierung.", | |||||
| "Observability ist Pflicht, nicht Luxus: Kein Pro-Level ohne Metriken, strukturierte Logs, Fault-Telemetrie und reproduzierbare Regressionen.", | |||||
| "Keine implizite Semantik: Alle Parameter müssen in Config, API, Runtime und Telemetrie exakt dasselbe bedeuten." | |||||
| ], | |||||
| "priority_model": { | |||||
| "P0": "Technische Perfektion und Determinismus", | |||||
| "P1": "Betriebssicherheit und Fehlerbeherrschung", | |||||
| "P2": "Hardware-Wahrheit und RF-Qualität", | |||||
| "P3": "Sichere und saubere Runtime-Steuerung", | |||||
| "P4": "Deployment-, Release- und Service-Reife" | |||||
| }, | |||||
| "workstreams": [ | |||||
| { | |||||
| "id": "WS-01", | |||||
| "priority": "P0", | |||||
| "title": "Deterministische Echtzeit-TX-Pipeline mit entkoppeltem Writer", | |||||
| "why": "Der aktuelle Single-Loop ist elegant, aber anfällig gegen Write-Spikes, Scheduling-Jitter und hardwarebedingte Blockaden. Pro-Level verlangt Entkopplung zwischen Erzeugung und Ausgabe.", | |||||
| "objective": "Generator/Upsampler und Hardwarewriter werden als getrennte Stufen mit kleinem, kontrolliertem Frame-Puffer betrieben. Der Writer darf kurzfristige Timing-Jitter absorbieren, ohne sofort den gesamten TX-Zyklus zu ruinieren.", | |||||
| "target_architecture": { | |||||
| "pipeline": [ | |||||
| "control-plane", | |||||
| "runtime supervisor", | |||||
| "generator worker", | |||||
| "optional upsampler worker", | |||||
| "bounded frame queue", | |||||
| "writer worker", | |||||
| "driver" | |||||
| ], | |||||
| "queue_policy": { | |||||
| "type": "bounded ring queue", | |||||
| "capacity_frames": 3, | |||||
| "default_behavior": "Producer füllt vor, Writer sendet in Echtzeit, Supervisor überwacht Queue-Füllstand", | |||||
| "allowed_strategies": [ | |||||
| "block producer kurzzeitig", | |||||
| "repeat last safe frame im Fault-Recovery-Modus", | |||||
| "mute frame im Sicherheitsmodus" | |||||
| ], | |||||
| "forbidden_strategies": [ | |||||
| "unbounded buffering", | |||||
| "still und heimlich alte Frames verwerfen ohne Counter/Log", | |||||
| "dynamisches Verhalten ohne Telemetrie" | |||||
| ] | |||||
| } | |||||
| }, | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-01-T1", | |||||
| "title": "FrameQueue einführen", | |||||
| "details": [ | |||||
| "Neue interne Queue-Struktur für CompositeFrame oder DeviceFrame einführen.", | |||||
| "Explizit festlegen, ob vor oder nach FMUpsampler gepuffert wird. Empfehlung: Puffern auf Device-Frame-Ebene, damit der Writer nur noch sendet.", | |||||
| "FrameQueue muss feste Kapazität, Füllstand, High-Watermark, Low-Watermark und Drop-/Mute-/Repeat-Counter liefern." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-01-T2", | |||||
| "title": "Writer-Worker einführen", | |||||
| "details": [ | |||||
| "Writer läuft in eigener Goroutine und besitzt alleinige Ownership über driver.Write().", | |||||
| "Nur der Writer darf Write- und Tune-nahe Timinginteraktionen mit dem Treiber koordinieren.", | |||||
| "Write-Dauer, Blockzeiten und Late-Events werden pro Frame gemessen." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-01-T3", | |||||
| "title": "Supervisor-Schicht einführen", | |||||
| "details": [ | |||||
| "Supervisor bewertet Queue-Füllstand, Late-Rate, Fehlerhäufigkeit und entscheidet über Normal/Fault/Recovery.", | |||||
| "Supervisor ist nicht nur Logik, sondern Runtime-State-Maschine." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Keine direkte synchrone Generate->Write-Kopplung mehr im Hauptpfad.", | |||||
| "Queue-Füllstand und Write-Latenz sind live sichtbar.", | |||||
| "Kurzzeitige Write-Spikes führen nicht sofort zu hörbaren Aussetzern oder unkontrolliertem Timing-Kollaps.", | |||||
| "Langlauf mit 6h Testdauer zeigt keine wachsende Drift im Kontrollpfad und keine ungebremste Fehlereskalation." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/app/engine.go", | |||||
| "internal/output/backend.go", | |||||
| "internal/platform/soapy.go" | |||||
| ], | |||||
| "example_interfaces": { | |||||
| "frame_queue_contract": "type FrameQueue interface { Push(frame *output.CompositeFrame) error; Pop(ctx context.Context) (*output.CompositeFrame, error); FillLevel() float64; Depth() int; Capacity() int; Stats() QueueStats }", | |||||
| "queue_stats_example": { | |||||
| "capacity": 3, | |||||
| "depth": 2, | |||||
| "fillLevel": 0.6667, | |||||
| "pushTimeouts": 0, | |||||
| "popTimeouts": 0, | |||||
| "droppedFrames": 0, | |||||
| "repeatedFrames": 0, | |||||
| "mutedFrames": 0 | |||||
| } | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-02", | |||||
| "priority": "P0", | |||||
| "title": "Explizite Runtime-State-Maschine und Fault-Handling", | |||||
| "why": "Aktuell existieren im Engine-State nur idle, running, stopping. Das reicht nicht für professionelles Fehlermanagement im Sendebetrieb.", | |||||
| "objective": "Einführen eines klaren Betriebsmodells mit Fault-, Recovery- und Muted-Zuständen. Jeder kritische Fehlerpfad endet in definiertem Verhalten.", | |||||
| "target_state_machine": { | |||||
| "states": [ | |||||
| "idle", | |||||
| "arming", | |||||
| "prebuffering", | |||||
| "running", | |||||
| "degraded", | |||||
| "muted", | |||||
| "faulted", | |||||
| "stopping" | |||||
| ], | |||||
| "transition_examples": [ | |||||
| { | |||||
| "from": "idle", | |||||
| "to": "arming", | |||||
| "trigger": "StartTX angefordert und Grundvalidierung erfolgreich" | |||||
| }, | |||||
| { | |||||
| "from": "arming", | |||||
| "to": "prebuffering", | |||||
| "trigger": "Driver gestartet, Queue erstellt, Generator bereit" | |||||
| }, | |||||
| { | |||||
| "from": "prebuffering", | |||||
| "to": "running", | |||||
| "trigger": "Queue-Minimum erreicht" | |||||
| }, | |||||
| { | |||||
| "from": "running", | |||||
| "to": "degraded", | |||||
| "trigger": "Late-Rate oberhalb Schwellwert oder Queue-Füllstand wiederholt kritisch" | |||||
| }, | |||||
| { | |||||
| "from": "degraded", | |||||
| "to": "muted", | |||||
| "trigger": "Writer kann keine sichere Ausgabe mehr garantieren" | |||||
| }, | |||||
| { | |||||
| "from": "muted", | |||||
| "to": "faulted", | |||||
| "trigger": "Persistenter Treiberfehler oder Recovery gescheitert" | |||||
| }, | |||||
| { | |||||
| "from": "muted", | |||||
| "to": "running", | |||||
| "trigger": "Queue und Treiber wieder stabil" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-02-T1", | |||||
| "title": "Fault-Klassifikation definieren", | |||||
| "details": [ | |||||
| "Treiberfehler", | |||||
| "Write-Time-Budget überschritten", | |||||
| "Queue leer", | |||||
| "Queue permanent überfüllt", | |||||
| "Signal-Selbsttest fehlgeschlagen", | |||||
| "unerlaubte Live-Konfigurationsänderung" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-02-T2", | |||||
| "title": "Reaktionsstrategie pro Fehlerklasse definieren", | |||||
| "details": [ | |||||
| "Warnen ohne Zustandswechsel", | |||||
| "degraded mit Countern", | |||||
| "muted mit stiller Trägerstrategie oder kompletter TX-Stille", | |||||
| "faulted mit manueller oder automatischer Recovery" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-02-T3", | |||||
| "title": "Event-Log und Fault-Historie einführen", | |||||
| "details": [ | |||||
| "Jeder Zustandswechsel ist auditierbar.", | |||||
| "Faults enthalten Ursache, Timestamp, Metriken und letzten bekannten Runtime-Kontext." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Jede kritische Fehlersituation führt in einen expliziten Zustand statt in implizites Weiterlaufen.", | |||||
| "Der aktuelle Runtime-State ist über API und Telemetrie jederzeit sichtbar.", | |||||
| "Degraded/Muted/Faulted lassen sich in Tests gezielt triggern und verifizieren." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/app/engine.go", | |||||
| "internal/control/control.go" | |||||
| ], | |||||
| "example_fault_policy": { | |||||
| "late_buffer_threshold_per_60s": 10, | |||||
| "queue_critical_fill_below": 0.1, | |||||
| "writer_error_burst": 3, | |||||
| "policy": [ | |||||
| "Bei 1-2 Einzelereignissen nur Counter erhöhen.", | |||||
| "Ab Burstschwelle auf degraded schalten.", | |||||
| "Wenn degraded > 5s anhält oder Write wiederholt fehlschlägt -> muted.", | |||||
| "Wenn muted nicht innerhalb definierter Frist stabilisiert werden kann -> faulted." | |||||
| ] | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-03", | |||||
| "priority": "P0", | |||||
| "title": "Semantische Korrektheit und harte Config-/Runtime-Konsistenz", | |||||
| "why": "Technische Perfektion scheitert oft nicht am DSP, sondern an stillen Semantikabweichungen zwischen Config, API, Live-Update und tatsächlicher Laufzeit.", | |||||
| "objective": "Ein einziger, eindeutig definierter Parameterraum. Jeder Wert hat exakt eine Bedeutung und identische Constraints in Config, HTTP-API, Runtime und Telemetrie.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-03-T1", | |||||
| "title": "Parameterinventar erstellen", | |||||
| "details": [ | |||||
| "Alle öffentlich und intern verwendeten Parameter inventarisieren.", | |||||
| "Für jeden Parameter: Typ, Einheit, Bereich, Default, Hot-Reload-Fähigkeit, Safety-Relevanz, Telemetrie-Name." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-03-T2", | |||||
| "title": "Validation vereinheitlichen", | |||||
| "details": [ | |||||
| "Config.Validate(), Engine.UpdateConfig() und API-Patch-Validierung dürfen nicht divergieren.", | |||||
| "Beispiel: outputDrive muss an allen Stellen denselben Bereich haben." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-03-T3", | |||||
| "title": "AppliedConfig einführen", | |||||
| "details": [ | |||||
| "Neben DesiredConfig muss es eine AppliedConfig geben.", | |||||
| "API-Antworten sollen nicht nur sagen, was gewünscht wurde, sondern was tatsächlich übernommen wurde." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Kein Parameter hat an zwei Stellen unterschiedliche Grenzwerte oder Einheiten.", | |||||
| "API kann DesiredConfig und AppliedConfig getrennt zurückgeben.", | |||||
| "Ungültige Hot-Updates werden deterministisch abgelehnt und nicht teilweise angewandt." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/config/config.go", | |||||
| "internal/app/engine.go", | |||||
| "internal/control/control.go" | |||||
| ], | |||||
| "example_parameter_schema": { | |||||
| "name": "fm.outputDrive", | |||||
| "unit": "logical composite drive factor", | |||||
| "type": "float64", | |||||
| "range": { | |||||
| "min": 0.0, | |||||
| "max": 3.0 | |||||
| }, | |||||
| "default": 1.0, | |||||
| "hot_reloadable": true, | |||||
| "safety_class": "medium", | |||||
| "notes": "Darf nicht mit hardware gain oder RF gain verwechselt werden." | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-04", | |||||
| "priority": "P1", | |||||
| "title": "Observability, Telemetrie und Diagnosefähigkeit", | |||||
| "why": "Ohne harte Telemetrie bleibt jedes Timing- oder RF-Problem ratenbasiert. Pro-Level braucht Metriken, strukturierte Logs und Diagnostik-Endpunkte.", | |||||
| "objective": "Vollständige Sichtbarkeit auf Runtime, Queue, Writer, Generator, RF-Selbsttests und API-Aktivität schaffen.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-04-T1", | |||||
| "title": "Strukturiertes Logging einführen", | |||||
| "details": [ | |||||
| "Einheitliches Logging-Backend nutzen.", | |||||
| "Keine verstreuten Printf-only Pfade als primäre Diagnose.", | |||||
| "Jeder Fault, State-Change und API-Eingriff wird strukturiert geloggt." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-04-T2", | |||||
| "title": "Prometheus-kompatible Metriken einführen", | |||||
| "details": [ | |||||
| "Engine-Metriken", | |||||
| "Writer-Metriken", | |||||
| "Queue-Metriken", | |||||
| "RDS-/Pilot-Selbsttest-Metriken", | |||||
| "Audio-Stream-Metriken", | |||||
| "Control-Plane-Metriken" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-04-T3", | |||||
| "title": "Debug- und Profiling-Endpunkte", | |||||
| "details": [ | |||||
| "pprof optional aktivierbar", | |||||
| "Build-Info, Git-Commit, Build-Tags, Backend, Plattform und Runtime-Version ausgeben" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Jeder relevante Betriebsaspekt ist per Runtime-Endpunkt oder Metrics-Endpunkt sichtbar.", | |||||
| "Fehlerfälle sind anhand von Logs und Countern nachvollziehbar, ohne den Code erneut zu lesen.", | |||||
| "Langlaufprobleme lassen sich zeitlich korrelieren." | |||||
| ], | |||||
| "example_metrics": [ | |||||
| "engine_chunks_generated_total", | |||||
| "engine_late_buffers_total", | |||||
| "engine_fault_transitions_total", | |||||
| "writer_write_duration_seconds", | |||||
| "queue_fill_ratio", | |||||
| "queue_dropped_frames_total", | |||||
| "queue_muted_frames_total", | |||||
| "driver_write_errors_total", | |||||
| "audio_stream_underruns_total", | |||||
| "audio_stream_overflows_total", | |||||
| "rf_selftest_pilot_db", | |||||
| "rf_selftest_rds_57k_db" | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/app/engine.go", | |||||
| "internal/control/control.go", | |||||
| "internal/platform/soapy.go", | |||||
| "internal/audio/stream.go" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-05", | |||||
| "priority": "P1", | |||||
| "title": "Sichere und erwachsene Control-Plane", | |||||
| "why": "Sobald TX start/stop, Frequenz, RDS-Text oder Live-Audio per HTTP steuerbar sind, ist die Control-Plane ein sicherheitsrelevanter Teil des Systems.", | |||||
| "objective": "API transport- und anwendungsseitig härten, state-aware machen und auditierbar gestalten.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-05-T1", | |||||
| "title": "Auth und Deploy-Modi definieren", | |||||
| "details": [ | |||||
| "Mindestens Token-Auth für Remote-Betrieb.", | |||||
| "Optionale mTLS-Unterstützung für geschützte Infrastrukturen.", | |||||
| "Explizite Betriebsmodi: localhost-only, trusted-lan, secured-remote." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-05-T2", | |||||
| "title": "HTTP-Server härten", | |||||
| "details": [ | |||||
| "ReadTimeout", | |||||
| "WriteTimeout", | |||||
| "IdleTimeout", | |||||
| "ReadHeaderTimeout", | |||||
| "Body-Size-Limits", | |||||
| "Content-Type-Validierung", | |||||
| "Method enforcement" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-05-T3", | |||||
| "title": "API semantisch aufräumen", | |||||
| "details": [ | |||||
| "DesiredConfig vs AppliedConfig vs RuntimeState", | |||||
| "Idempotente Start/Stop-Endpunkte", | |||||
| "Transaktionsartige Apply-/Reject-Antworten für Patches", | |||||
| "Audit-Log pro wirksamem Eingriff" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Kein ungeschützter Remote-TX-Betrieb im Standardmodus.", | |||||
| "API liefert deterministische und vollständige Antworten.", | |||||
| "Große oder falsche Requests können das System nicht unkontrolliert stressen." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/control/control.go", | |||||
| "cmd/fmrtx/main.go" | |||||
| ], | |||||
| "example_patch_response": { | |||||
| "ok": true, | |||||
| "requestedChangeId": "cfg-2026-04-05T12:00:00Z-0001", | |||||
| "desired": { | |||||
| "frequencyMHz": 100.2, | |||||
| "ps": "JAN FM" | |||||
| }, | |||||
| "applied": { | |||||
| "frequencyMHz": 100.2, | |||||
| "ps": "JAN FM" | |||||
| }, | |||||
| "state": "running", | |||||
| "warnings": [] | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-06", | |||||
| "priority": "P2", | |||||
| "title": "Hardware-in-the-loop und externe RF-Wahrheitsprüfung", | |||||
| "why": "Ein DSP-System ist erst dann pro-tauglich, wenn es auf echter Hardware und mit externem Decoder/Messergebnis wiederholt korrekt ist.", | |||||
| "objective": "Nightly- oder manuell triggerbare Hardware-Regressionen etablieren, die nicht nur intern, sondern extern prüfen, was tatsächlich gesendet wird.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-06-T1", | |||||
| "title": "Loopback-/Capture-Setup definieren", | |||||
| "details": [ | |||||
| "Referenz-SDR oder definierter externer Empfänger als Capture-Seite.", | |||||
| "Leistungsschutz und Dummy-Load-Konzept klar dokumentieren.", | |||||
| "Standard-Testfrequenz, Standard-Device-Rate und Standard-Testdauer festlegen." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-06-T2", | |||||
| "title": "Automatisierte Analyse bauen", | |||||
| "details": [ | |||||
| "Pilotenergie 19 kHz", | |||||
| "Stereoenergie 38 kHz", | |||||
| "RDS-Energie 57 kHz", | |||||
| "Deviation/Composite-Level", | |||||
| "Trägergenauigkeit", | |||||
| "RDS-Decodierbarkeit und Fehlerrate", | |||||
| "Langlaufdrift" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-06-T3", | |||||
| "title": "Externen Decoder in Regression einbinden", | |||||
| "details": [ | |||||
| "PS und RT müssen extern decodierbar und stabil sein.", | |||||
| "Nicht nur intern erzeugte Bits prüfen." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Definierte Testsignale werden extern reproduzierbar korrekt empfangen.", | |||||
| "Pilot-, Stereo- und RDS-Komponenten liegen mit stabilen Pegeln an den erwarteten Frequenzen.", | |||||
| "Langlauftests zeigen keine schleichende Entwertung der Sendekette." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/offline/spectral_test.go", | |||||
| "internal/platform/soapy.go", | |||||
| "scripts/*" | |||||
| ], | |||||
| "example_hil_report": { | |||||
| "device": "pluto", | |||||
| "testDurationMinutes": 30, | |||||
| "pilot19kDetected": true, | |||||
| "stereo38kDetected": true, | |||||
| "rds57kDetected": true, | |||||
| "rdsDecodeSuccessRate": 0.998, | |||||
| "maxCarrierErrorHz": 12.0, | |||||
| "maxLateBuffers": 0, | |||||
| "result": "pass" | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-07", | |||||
| "priority": "P2", | |||||
| "title": "Device-aware Capability- und Kalibrierungsmodell", | |||||
| "why": "Ein generisches Backend-Modell reicht nicht, wenn unterschiedliche SDRs unterschiedliche Raten, Gains, Latenzen und Abweichungen haben.", | |||||
| "objective": "Pro Gerät bzw. Gerätetyp bekannte Fähigkeiten und Kalibrierungen explizit abbilden.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-07-T1", | |||||
| "title": "Capabilities ausbauen", | |||||
| "details": [ | |||||
| "Sample-Rate-Sets oder Bereiche", | |||||
| "Gain-Semantik", | |||||
| "Frequenzraster", | |||||
| "MTU-/Buffer-Empfehlungen", | |||||
| "Minimale stabile Chunkgrößen", | |||||
| "Tune-Latenzverhalten" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-07-T2", | |||||
| "title": "Kalibrierungsprofil einführen", | |||||
| "details": [ | |||||
| "frequencyOffsetHz", | |||||
| "deviationScale", | |||||
| "mpxGainCalibration", | |||||
| "driverChunkRecommendation", | |||||
| "safeDefaultGain" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-07-T3", | |||||
| "title": "Device-aware Validation", | |||||
| "details": [ | |||||
| "Config nicht nur gegen generische Regeln validieren, sondern gegen bekannte Device-Fähigkeiten.", | |||||
| "Nicht unterstützte Raten oder gefährliche Kombinationen früh blockieren." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Treiber und Runtime wissen explizit, was das konkrete Device sicher kann.", | |||||
| "Kalibrierte Geräte liefern reproduzierbarere RF-Ergebnisse.", | |||||
| "Fehlkonfigurationen werden vor TX-Beginn erkannt." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/platform/soapy.go", | |||||
| "internal/config/config.go" | |||||
| ], | |||||
| "example_calibration_profile": { | |||||
| "deviceId": "pluto-serial-1234", | |||||
| "sampleRateHz": 1000000, | |||||
| "frequencyOffsetHz": -18.0, | |||||
| "deviationScale": 0.97, | |||||
| "mpxGainCalibration": 1.03, | |||||
| "safeDefaultGainDb": -20.0, | |||||
| "preferredChunkMs": 50 | |||||
| } | |||||
| }, | |||||
| { | |||||
| "id": "WS-08", | |||||
| "priority": "P2", | |||||
| "title": "Signal-Selbstüberwachung im Betrieb", | |||||
| "why": "Nur Post-mortem-Messungen reichen nicht. Das System soll im Betrieb merken, wenn Pilot, RDS oder Composite auffällig werden.", | |||||
| "objective": "Leichtgewichtige In-Band-Selbsttests auf Chunk-Basis oder in Intervallen ausführen und in Fault-Logik einspeisen.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-08-T1", | |||||
| "title": "Chunk-basierte RF-Checks definieren", | |||||
| "details": [ | |||||
| "Goertzel oder kleine FFT für 19 kHz, 38 kHz und 57 kHz", | |||||
| "Composite-Clipping- und Pegelindikatoren", | |||||
| "Optional Deviation-Schätzer" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-08-T2", | |||||
| "title": "Anomalieerkennung definieren", | |||||
| "details": [ | |||||
| "Pilot fehlt", | |||||
| "RDS ungewöhnlich schwach", | |||||
| "unerwartete Composite-Energieverteilung", | |||||
| "langsame Drift" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Runtime kann relevante RF-Anomalien erkennen und melden.", | |||||
| "Selbsttests sind ausreichend billig, um die Echtzeitfähigkeit nicht zu gefährden." | |||||
| ], | |||||
| "affected_files": [ | |||||
| "internal/dsp/goertzel.go", | |||||
| "internal/offline/generator.go", | |||||
| "internal/app/engine.go" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-09", | |||||
| "priority": "P3", | |||||
| "title": "Teststrategie von Unit-Tests zu echter Qualitätsabsicherung erweitern", | |||||
| "why": "Viele Tests sind gut. Die richtigen Testklassen sind besser. Pro-Level verlangt deterministische Regressionen, Fuzzing und Concurrency-Tests.", | |||||
| "objective": "Testpyramide so ausbauen, dass Signal, Runtime und API gleichermaßen abgesichert sind.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-09-T1", | |||||
| "title": "Golden-Vector-Tests", | |||||
| "details": [ | |||||
| "Definierte Inputsignale und erwartete Analysewerte festschreiben.", | |||||
| "Nicht nur boolsche Pass/Fail-Aussagen, sondern tolerierte numerische Fenster." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-09-T2", | |||||
| "title": "Long-run Regressionen", | |||||
| "details": [ | |||||
| "Tausende Chunks durchlaufen lassen.", | |||||
| "Boundary-Continuity, Drift, Queue-Stabilität, Writer-Stabilität prüfen." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-09-T3", | |||||
| "title": "Race Detector, Fuzzing und API-Mutation-Tests", | |||||
| "details": [ | |||||
| "Konfigurationspatches", | |||||
| "RDS-Texte", | |||||
| "Audio-Ingest", | |||||
| "Start/Stop-Rennen", | |||||
| "gleichzeitige Live-Updates" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Es gibt Regressionen für DSP, Runtime, API und HIL.", | |||||
| "Race Detector und Fuzzing finden keine bekannten kritischen Pfade mehr.", | |||||
| "Nightly-Regressions geben maschinenlesbare Berichte aus." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-10", | |||||
| "priority": "P4", | |||||
| "title": "Service-Reife, Packaging und Reproduzierbarkeit", | |||||
| "why": "Ein System ist nicht professionell, wenn nur der Autor es zuverlässig starten kann.", | |||||
| "objective": "Saubere Build-, Release- und Betriebsartefakte bereitstellen.", | |||||
| "implementation_tasks": [ | |||||
| { | |||||
| "id": "WS-10-T1", | |||||
| "title": "Reproduzierbare Builds", | |||||
| "details": [ | |||||
| "Build-Flags, Version, Commit und Tags ins Binary schreiben.", | |||||
| "Artefakte pro Zielplattform konsistent erzeugen." | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-10-T2", | |||||
| "title": "Service-Units und Beispiel-Deployments", | |||||
| "details": [ | |||||
| "systemd unit", | |||||
| "EnvironmentFile-Unterstützung", | |||||
| "Beispielkonfigurationen pro Backend" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "id": "WS-10-T3", | |||||
| "title": "Migrations- und Schema-Versionierung", | |||||
| "details": [ | |||||
| "Config-Versionen einführen.", | |||||
| "Klare Migrationsstrategie für ältere JSON-Konfigurationen." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "acceptance_criteria": [ | |||||
| "Standardisierte Start- und Betriebswege existieren.", | |||||
| "Build- und Runtime-Version sind eindeutig sichtbar.", | |||||
| "Alte Konfigurationen können kontrolliert migriert werden." | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "implementation_sequence": [ | |||||
| { | |||||
| "order": 1, | |||||
| "workstreams": [ | |||||
| "WS-03", | |||||
| "WS-01", | |||||
| "WS-02" | |||||
| ], | |||||
| "reason": "Erst muss die Semantik stimmen, dann die deterministische Pipeline, dann das Fault-Modell. Sonst baut man saubere Infrastruktur auf falschen Annahmen." | |||||
| }, | |||||
| { | |||||
| "order": 2, | |||||
| "workstreams": [ | |||||
| "WS-04", | |||||
| "WS-05" | |||||
| ], | |||||
| "reason": "Sobald Runtime-Struktur sauber ist, müssen Sichtbarkeit und sichere Steuerung folgen. Sonst ist der neue Kern schwer überprüfbar und riskant bedienbar." | |||||
| }, | |||||
| { | |||||
| "order": 3, | |||||
| "workstreams": [ | |||||
| "WS-06", | |||||
| "WS-07", | |||||
| "WS-08" | |||||
| ], | |||||
| "reason": "Jetzt wird die Hardware-Wahrheit und RF-Qualität fest verdrahtet: HIL, Kalibrierung und laufende Signalkontrolle." | |||||
| }, | |||||
| { | |||||
| "order": 4, | |||||
| "workstreams": [ | |||||
| "WS-09", | |||||
| "WS-10" | |||||
| ], | |||||
| "reason": "Zum Schluss werden Regressionstiefe, Packaging und Service-Reife maximal professionalisiert." | |||||
| } | |||||
| ], | |||||
| "cross_cutting_rules": { | |||||
| "musts": [ | |||||
| "Jeder neue Runtime-Zustand muss per API und Telemetrie sichtbar sein.", | |||||
| "Jede neue Recovery- oder Drop-/Mute-Strategie braucht Counter, Logs und Tests.", | |||||
| "Keine neue Konfigurationsoption ohne klaren Typ, Bereich, Einheit, Default und Hot-Reload-Klassifikation.", | |||||
| "Hardware-nahe Änderungen brauchen mindestens Simulations- und HIL-Validierung.", | |||||
| "Alle Faults müssen eine maschinenlesbare Ursache und eine menschenlesbare Zusammenfassung haben." | |||||
| ], | |||||
| "must_not": [ | |||||
| "Keine unbounded Queues.", | |||||
| "Keine stillen Fallbacks ohne Telemetrie.", | |||||
| "Keine teilweise angewandten Live-Config-Änderungen ohne explizite Rückmeldung.", | |||||
| "Keine unterschiedlichen Grenzwerte zwischen Config, API und Runtime.", | |||||
| "Keine sicherheitsrelevanten HTTP-Endpunkte ohne Härtung im Remote-Betrieb." | |||||
| ] | |||||
| }, | |||||
| "concrete_examples": { | |||||
| "example_runtime_status": { | |||||
| "state": "degraded", | |||||
| "substate": "writer_backpressure", | |||||
| "engine": { | |||||
| "chunksProduced": 128443, | |||||
| "lateBuffers": 7, | |||||
| "underruns": 0, | |||||
| "maxCycleMs": 51.12, | |||||
| "maxWriteMs": 49.91 | |||||
| }, | |||||
| "queue": { | |||||
| "capacity": 3, | |||||
| "depth": 1, | |||||
| "fillLevel": 0.3333, | |||||
| "droppedFrames": 0, | |||||
| "repeatedFrames": 2, | |||||
| "mutedFrames": 0 | |||||
| }, | |||||
| "driver": { | |||||
| "txEnabled": true, | |||||
| "streamActive": true, | |||||
| "samplesWritten": 64221500, | |||||
| "slowWrites": 4 | |||||
| }, | |||||
| "lastFault": { | |||||
| "code": "WRITER-LATE-BURST", | |||||
| "at": "2026-04-05T12:34:56Z", | |||||
| "message": "Write-Latenz wiederholt über Chunk-Budget" | |||||
| } | |||||
| }, | |||||
| "example_fault_event": { | |||||
| "eventId": "fault-000184", | |||||
| "severity": "error", | |||||
| "stateBefore": "running", | |||||
| "stateAfter": "muted", | |||||
| "code": "QUEUE-EMPTY-RECOVERY-FAILED", | |||||
| "message": "Queue blieb trotz Recovery leer; Ausgang wurde auf Mute gesetzt", | |||||
| "metrics": { | |||||
| "queueFillLevel": 0.0, | |||||
| "lateBuffersLast60s": 14, | |||||
| "writerErrorsLast60s": 3 | |||||
| } | |||||
| }, | |||||
| "example_rollout_plan": { | |||||
| "milestone_1": "Semantik und State-Maschine stabil", | |||||
| "milestone_2": "Entkoppelter Writer mit Telemetrie", | |||||
| "milestone_3": "Sichere API und Audit-Log", | |||||
| "milestone_4": "HIL-Regression und Kalibrierung", | |||||
| "milestone_5": "Nightly-Qualitätsgates und reproduzierbare Releases" | |||||
| } | |||||
| }, | |||||
| "definition_of_done": { | |||||
| "technical": [ | |||||
| "TX-Pfad ist deterministisch entkoppelt und fault-tolerant.", | |||||
| "DesiredConfig, AppliedConfig und RuntimeState sind sauber getrennt.", | |||||
| "Alle kritischen Fehler führen in explizite Zustände.", | |||||
| "RF-Signalqualität ist intern und extern nachweisbar.", | |||||
| "Metriken, strukturierte Logs und Diagnosepfade sind vollständig." | |||||
| ], | |||||
| "operational": [ | |||||
| "System läuft im Langlauftest stabil.", | |||||
| "Remote-Bedienung ist gehärtet und auditierbar.", | |||||
| "Gerätespezifische Fähigkeiten und Kalibrierungen sind modelliert.", | |||||
| "Builds und Deployments sind reproduzierbar." | |||||
| ], | |||||
| "quality_gates": [ | |||||
| "Unit- und Integrationssuite grün", | |||||
| "Race Detector grün", | |||||
| "Fuzzing ohne kritische Findings", | |||||
| "HIL-Regression grün", | |||||
| "Soak-Test grün" | |||||
| ] | |||||
| }, | |||||
| "final_instruction_to_ai_team": { | |||||
| "summary": "Nicht blind Features ergänzen. Zuerst die Semantik und den Runtime-Kern hart machen. Dann Observability und sichere Steuerung. Danach Hardware-Wahrheit, Kalibrierung und Nightly-Regressions. Alles, was nicht deterministisch, messbar und fault-beherrscht ist, ist noch nicht pro-level.", | |||||
| "first_step_now": [ | |||||
| "WS-03 beginnen: Parameterinventar und semantische Vereinheitlichung.", | |||||
| "Danach direkt WS-01 und WS-02 in einem Architektur-Branch umsetzen." | |||||
| ] | |||||
| } | |||||
| } | |||||