Skip to content

Commit e38d1cd

Browse files
committed
fix: expose quality signal API schemas
Runtime session responses already include grouped quality signal data, but the OpenAPI schema hid that field from generated clients. That left frontend callers without the typed quality_signals object even though the server returned it. Signal evidence sessions also need to remain a non-null typed array in the schema so client generation keeps DbSignalSessionExample instead of collapsing the field to any[].
1 parent 8cbccbc commit e38d1cd

8 files changed

Lines changed: 105 additions & 35 deletions

File tree

frontend/src/lib/api/generated/index.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/lib/api/generated/models/DbQualitySignals.ts

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/lib/api/generated/models/DbSession.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/lib/api/generated/models/DbSignalSessionsResponse.ts

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/lib/api/generated/models/ServiceSessionDetail.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/db/analytics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2992,7 +2992,7 @@ type SignalCalibration struct {
29922992
// an aggregate signal, including the best available message excerpt.
29932993
type SignalSessionsResponse struct {
29942994
Signal string `json:"signal"`
2995-
Sessions []SignalSessionExample `json:"sessions"`
2995+
Sessions []SignalSessionExample `json:"sessions" nullable:"false"`
29962996
}
29972997

29982998
type SignalSessionExample struct {

internal/db/sessions.go

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ type QualitySignals struct {
183183
// deterministic quality-signal columns. Version 0 means the row has
184184
// not gone through the Phase 3 signal write/backfill path yet.
185185
func (s Session) StoredQualitySignals() *QualitySignals {
186+
if s.QualitySignals != nil {
187+
return s.QualitySignals
188+
}
186189
if s.QualitySignalVersion <= 0 {
187190
return nil
188191
}
@@ -201,6 +204,7 @@ func (s Session) StoredQualitySignals() *QualitySignals {
201204
// ApplyQualitySignals maps the grouped API representation back to the
202205
// scalar persistence fields used internally.
203206
func (s *Session) ApplyQualitySignals(qs *QualitySignals) {
207+
s.QualitySignals = qs
204208
if qs == nil {
205209
s.QualitySignalVersion = 0
206210
s.ShortPromptCount = 0
@@ -273,39 +277,42 @@ type Session struct {
273277
IsAutomated bool `json:"is_automated"`
274278

275279
// Session signals (computed from messages/tool_calls).
276-
ToolFailureSignalCount int `json:"tool_failure_signal_count"`
277-
ToolRetryCount int `json:"tool_retry_count"`
278-
EditChurnCount int `json:"edit_churn_count"`
279-
ConsecutiveFailureMax int `json:"consecutive_failure_max"`
280-
Outcome string `json:"outcome"`
281-
OutcomeConfidence string `json:"outcome_confidence"`
282-
EndedWithRole string `json:"ended_with_role"`
283-
FinalFailureStreak int `json:"final_failure_streak"`
284-
SignalsPendingSince *string `json:"signals_pending_since,omitempty"`
285-
CompactionCount int `json:"compaction_count"`
286-
MidTaskCompactionCount int `json:"mid_task_compaction_count"`
287-
ContextPressureMax *float64 `json:"context_pressure_max,omitempty"`
288-
HealthScore *int `json:"health_score,omitempty"`
289-
HealthGrade *string `json:"health_grade,omitempty"`
290-
HasToolCalls bool `json:"-"`
291-
HasContextData bool `json:"-"`
292-
SecretLeakCount int `json:"secret_leak_count"`
293-
SecretsRulesVersion string `json:"-"`
294-
QualitySignalVersion int `json:"-"`
295-
ShortPromptCount int `json:"-"`
296-
UnstructuredStart bool `json:"-"`
297-
MissingSuccessCriteriaCount int `json:"-"`
298-
MissingVerificationCount int `json:"-"`
299-
DuplicatePromptCount int `json:"-"`
300-
NoCodeContextCount int `json:"-"`
301-
RunawayToolLoopCount int `json:"-"`
302-
DataVersion int `json:"-"`
303-
Cwd string `json:"cwd,omitempty"`
304-
GitBranch string `json:"git_branch,omitempty"`
305-
SourceSessionID string `json:"source_session_id,omitempty"`
306-
SourceVersion string `json:"source_version,omitempty"`
307-
ParserMalformedLines int `json:"parser_malformed_lines,omitempty"`
308-
IsTruncated bool `json:"is_truncated,omitempty"`
280+
ToolFailureSignalCount int `json:"tool_failure_signal_count"`
281+
ToolRetryCount int `json:"tool_retry_count"`
282+
EditChurnCount int `json:"edit_churn_count"`
283+
ConsecutiveFailureMax int `json:"consecutive_failure_max"`
284+
Outcome string `json:"outcome"`
285+
OutcomeConfidence string `json:"outcome_confidence"`
286+
EndedWithRole string `json:"ended_with_role"`
287+
FinalFailureStreak int `json:"final_failure_streak"`
288+
SignalsPendingSince *string `json:"signals_pending_since,omitempty"`
289+
CompactionCount int `json:"compaction_count"`
290+
MidTaskCompactionCount int `json:"mid_task_compaction_count"`
291+
ContextPressureMax *float64 `json:"context_pressure_max,omitempty"`
292+
HealthScore *int `json:"health_score,omitempty"`
293+
HealthGrade *string `json:"health_grade,omitempty"`
294+
// QualitySignals mirrors the scalar persistence fields below for API
295+
// schema and JSON transport.
296+
QualitySignals *QualitySignals `json:"quality_signals,omitempty"`
297+
HasToolCalls bool `json:"-"`
298+
HasContextData bool `json:"-"`
299+
SecretLeakCount int `json:"secret_leak_count"`
300+
SecretsRulesVersion string `json:"-"`
301+
QualitySignalVersion int `json:"-"`
302+
ShortPromptCount int `json:"-"`
303+
UnstructuredStart bool `json:"-"`
304+
MissingSuccessCriteriaCount int `json:"-"`
305+
MissingVerificationCount int `json:"-"`
306+
DuplicatePromptCount int `json:"-"`
307+
NoCodeContextCount int `json:"-"`
308+
RunawayToolLoopCount int `json:"-"`
309+
DataVersion int `json:"-"`
310+
Cwd string `json:"cwd,omitempty"`
311+
GitBranch string `json:"git_branch,omitempty"`
312+
SourceSessionID string `json:"source_session_id,omitempty"`
313+
SourceVersion string `json:"source_version,omitempty"`
314+
ParserMalformedLines int `json:"parser_malformed_lines,omitempty"`
315+
IsTruncated bool `json:"is_truncated,omitempty"`
309316

310317
DeletedAt *string `json:"deleted_at,omitempty"`
311318
TerminationStatus *string `json:"termination_status,omitempty"`

internal/server/server_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,49 @@ func TestOpenAPIEndpointDocumentsEnumsAndRequestBodies(t *testing.T) {
632632
assert.Equal(t, []string{"auto", "custom", "clipboard"}, mode.Enum)
633633
}
634634

635+
func TestOpenAPIEndpointDocumentsQualitySignalResponses(t *testing.T) {
636+
te := setup(t)
637+
638+
w := te.get(t, "/api/openapi.json")
639+
640+
require.Equal(t, http.StatusOK, w.Code, "body: %s", w.Body.String())
641+
type openAPISchema struct {
642+
Ref string `json:"$ref"`
643+
Type any `json:"type"`
644+
Items *openAPISchema `json:"items"`
645+
Properties map[string]openAPISchema `json:"properties"`
646+
}
647+
var spec struct {
648+
Components struct {
649+
Schemas map[string]openAPISchema `json:"schemas"`
650+
} `json:"components"`
651+
}
652+
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &spec))
653+
654+
for _, schemaName := range []string{"DbSession", "ServiceSessionDetail"} {
655+
schema, ok := spec.Components.Schemas[schemaName]
656+
require.True(t, ok, "schema %s missing", schemaName)
657+
require.Contains(t, schema.Properties, "quality_signals",
658+
"schema %s should expose runtime quality_signals", schemaName)
659+
assert.Equal(t,
660+
"#/components/schemas/DbQualitySignals",
661+
schema.Properties["quality_signals"].Ref,
662+
"schema %s quality_signals ref", schemaName)
663+
}
664+
665+
response, ok := spec.Components.Schemas["DbSignalSessionsResponse"]
666+
require.True(t, ok, "schema DbSignalSessionsResponse missing")
667+
sessions, ok := response.Properties["sessions"]
668+
require.True(t, ok, "DbSignalSessionsResponse.sessions missing")
669+
assert.Equal(t, "array", sessions.Type,
670+
"sessions should be a non-null array so the generated client keeps item type")
671+
require.NotNil(t, sessions.Items, "sessions.items missing")
672+
assert.Equal(t,
673+
"#/components/schemas/DbSignalSessionExample",
674+
sessions.Items.Ref,
675+
"sessions item schema")
676+
}
677+
635678
func TestOpenAPIEndpointDocumentsImportResponseContentTypes(t *testing.T) {
636679
te := setup(t)
637680

0 commit comments

Comments
 (0)