| 1 | package serve |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "io" |
| 6 | "net/http" |
| 7 | |
| 8 | "reasonix/internal/control" |
| 9 | ) |
| 10 | |
| 11 | type goalDiagnosticExporter interface { |
| 12 | ExportGoalDiagnostics(context.Context, control.GoalDiagnosticMetadata) ([]byte, error) |
| 13 | } |
| 14 | |
| 15 | type goalDiagnosticStreamExporter interface { |
| 16 | WriteGoalDiagnostics(context.Context, io.Writer, control.GoalDiagnosticMetadata) error |
| 17 | } |
| 18 | |
| 19 | // goalDiagnostics exports the authoritative, fully flushed v3 event stream. |
| 20 | // It is session-fenced but read-only and therefore remains available to a |
| 21 | // spectator inspecting a session owned by another Reasonix surface. |
| 22 | func (s *Server) goalDiagnostics(w http.ResponseWriter, r *http.Request) { |
| 23 | s.bindMu.Lock() |
| 24 | defer s.bindMu.Unlock() |
| 25 | if !s.validateExpectedSessionLocked(w, r) { |
| 26 | return |
| 27 | } |
| 28 | controller := s.ctl() |
| 29 | streamer, streamOK := controller.(goalDiagnosticStreamExporter) |
| 30 | exporter, exportOK := controller.(goalDiagnosticExporter) |
| 31 | if !streamOK && !exportOK { |
| 32 | http.Error(w, "goal diagnostics require goal-lifecycle-v2", http.StatusNotImplemented) |
| 33 | return |
| 34 | } |
| 35 | w.Header().Set("Content-Type", "application/json") |
| 36 | w.Header().Set("Content-Disposition", `attachment; filename="reasonix-goal-diagnostics.json"`) |
| 37 | metadata := control.GoalDiagnosticMetadata{Capabilities: s.capabilities()} |
| 38 | if streamOK { |
| 39 | if err := streamer.WriteGoalDiagnostics(r.Context(), w, metadata); err != nil { |
| 40 | // The response may already contain a valid prefix. Closing the body is |
| 41 | // the only honest signal once streaming has started; never append a |
| 42 | // second JSON error document to the artifact. |
| 43 | return |
| 44 | } |
| 45 | return |
| 46 | } |
| 47 | payload, err := exporter.ExportGoalDiagnostics(r.Context(), metadata) |
| 48 | if err != nil { |
| 49 | http.Error(w, err.Error(), http.StatusConflict) |
| 50 | return |
| 51 | } |
| 52 | _, _ = w.Write(payload) |
| 53 | } |
| 54 |