package workbenchruntime import ( "context" "database/sql" "encoding/json" "errors" "fmt" "log" "net/http" "os" "os/signal" "strconv" "strings" "syscall" "time" _ "github.com/jackc/pgx/v4/stdlib" ) const serviceID = "hwlab-workbench-runtime" const ( sessionSummaryQueryMaxAttempts = 2 sessionSummaryQueryAttemptTimeout = 2 * time.Second sessionSummaryQueryRetryBackoff = 150 * time.Millisecond sessionsSummaryCacheClass = "sessions.summary" terminalTurnCacheClass = "turn.terminal.snapshot" terminalTracePageCacheClass = "trace.terminal.events" ) type sessionsCacheEntry struct { CachedAt string `json:"cachedAt"` Payload map[string]any `json:"payload"` PayloadBytes int `json:"payloadBytes"` TTLMillis int64 `json:"ttlMs"` ProjectionRevision string `json:"projectionRevision,omitempty"` ProjectionSeqMax int64 `json:"projectionSeqMax,omitempty"` SessionUpdatedAtMax string `json:"sessionUpdatedAtMax,omitempty"` SessionCount int `json:"sessionCount,omitempty"` ValuesRedacted bool `json:"valuesRedacted"` } type factsCacheEntry struct { CachedAt string `json:"cachedAt"` Payload map[string]any `json:"payload"` PayloadBytes int `json:"payloadBytes"` TTLMillis int64 `json:"ttlMs"` ValuesRedacted bool `json:"valuesRedacted"` } type factsCachePlan struct { Class string Key string Meta map[string]any TraceID string ProjectionSeq int64 ProjectionStatus string ProjectionHealth string Status string } type sessionsSummaryRevision struct { ProjectionRevision string ProjectionSeqMax int64 SessionUpdatedAtMax string SessionCount int RevisionSource string } type Config struct { Addr string DatabaseURL string QueryTimeout time.Duration ReadinessDelay time.Duration PoolMax int Cache cacheConfig } type Server struct { config Config db *sql.DB cache *derivedCache mux *http.ServeMux } var workbenchRuntimeReadIndexes = []string{ "CREATE INDEX IF NOT EXISTS idx_workbench_sessions_updated ON workbench_sessions(updated_at DESC, session_id)", "CREATE INDEX IF NOT EXISTS idx_workbench_sessions_owner_updated ON workbench_sessions(owner_user_id, updated_at DESC, session_id)", "CREATE INDEX IF NOT EXISTS idx_workbench_sessions_status_updated ON workbench_sessions(status, updated_at DESC, session_id)", "CREATE INDEX IF NOT EXISTS idx_workbench_messages_session_updated ON workbench_messages(session_id, updated_at ASC, message_id)", "CREATE INDEX IF NOT EXISTS idx_workbench_messages_session_role_updated ON workbench_messages(session_id, role, updated_at ASC, message_id)", "CREATE INDEX IF NOT EXISTS idx_workbench_turns_trace_updated ON workbench_turns(trace_id, updated_at DESC)", "CREATE INDEX IF NOT EXISTS idx_workbench_projection_checkpoints_trace_updated ON workbench_projection_checkpoints(trace_id, updated_at DESC)", } type factQuery struct { Families []string `json:"families"` FactFamilies []string `json:"factFamilies"` Limit int `json:"limit"` AfterProjectedSeq int64 `json:"afterProjectedSeq"` Order string `json:"order"` SessionsOrder string `json:"sessionsOrder"` MessagesOrder string `json:"messagesOrder"` PartsOrder string `json:"partsOrder"` TurnsOrder string `json:"turnsOrder"` CheckpointsOrder string `json:"checkpointsOrder"` SessionProjection string `json:"sessionProjection"` SessionID string `json:"sessionId"` SessionIDs []string `json:"sessionIds"` TraceID string `json:"traceId"` TraceIDs []string `json:"traceIds"` MessageID string `json:"messageId"` MessageIDs []string `json:"messageIds"` TurnID string `json:"turnId"` TurnIDs []string `json:"turnIds"` Status string `json:"status"` Statuses []string `json:"statuses"` OwnerUserID string `json:"ownerUserId"` OwnerUserIDs []string `json:"ownerUserIds"` ConversationID string `json:"conversationId"` ConversationIDs []string `json:"conversationIds"` EventType string `json:"eventType"` EventTypes []string `json:"eventTypes"` ProjectionStatus string `json:"projectionStatus"` ProjectionStatuses []string `json:"projectionStatuses"` ProjectionHealth string `json:"projectionHealth"` ProjectionHealths []string `json:"projectionHealths"` RunID string `json:"runId"` RunIDs []string `json:"runIds"` CommandID string `json:"commandId"` CommandIDs []string `json:"commandIds"` } type traceEventsQuery struct { TraceID string `json:"traceId"` SessionID string `json:"sessionId"` WorkerSessionID string `json:"workerSessionId"` Level string `json:"level"` Limit int `json:"limit"` } type outboxQuery struct { AfterSeq int64 `json:"afterSeq"` Limit int `json:"limit"` TraceID string `json:"traceId"` SessionID string `json:"sessionId"` } type sessionListQuery struct { Limit int `json:"limit"` Offset int `json:"offset"` Cursor string `json:"cursor"` IncludeSessionID string `json:"includeSessionId"` Actor sessionActor `json:"actor"` } type sessionActor struct { ID string `json:"id"` Role string `json:"role"` } type fieldColumn struct { field string values []string column string } func Run(ctx context.Context) error { config := configFromEnv() if strings.TrimSpace(config.DatabaseURL) == "" { return errors.New("HWLAB_WORKBENCH_RUNTIME_DB_URL or HWLAB_CLOUD_DB_URL is required") } db, err := sql.Open("pgx", config.DatabaseURL) if err != nil { return err } defer db.Close() if config.PoolMax > 0 { db.SetMaxOpenConns(config.PoolMax) db.SetMaxIdleConns(config.PoolMax) } if err := ensureWorkbenchRuntimeReadIndexes(ctx, db); err != nil { return err } server := NewServer(config, db) defer server.Close() httpServer := &http.Server{Addr: config.Addr, Handler: server.otelHTTPHandler(server.mux), ReadHeaderTimeout: 5 * time.Second} errCh := make(chan error, 1) go func() { log.Printf("%s listening addr=%s", serviceID, config.Addr) errCh <- httpServer.ListenAndServe() }() stopCh := make(chan os.Signal, 1) signal.Notify(stopCh, syscall.SIGINT, syscall.SIGTERM) select { case <-ctx.Done(): case <-stopCh: case err := <-errCh: if err != nil && !errors.Is(err, http.ErrServerClosed) { return err } } shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return httpServer.Shutdown(shutdownCtx) } func ensureWorkbenchRuntimeReadIndexes(ctx context.Context, db *sql.DB) error { ensureCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() for _, statement := range workbenchRuntimeReadIndexes { if _, err := db.ExecContext(ensureCtx, statement); err != nil { return fmt.Errorf("ensure workbench runtime read index: %w", err) } } return nil } func NewServer(config Config, db *sql.DB) *Server { server := &Server{config: config, db: db, cache: newDerivedCache(config.Cache), mux: http.NewServeMux()} server.routes() return server } func (s *Server) Close() error { if s == nil || s.cache == nil { return nil } return s.cache.Close() } func (s *Server) routes() { s.mux.HandleFunc("/health/live", s.handleLive) s.mux.HandleFunc("/health/ready", s.handleReady) s.mux.HandleFunc("/metrics", s.handleMetrics) s.mux.HandleFunc("/v1/workbench-runtime/facts", s.handleFacts) s.mux.HandleFunc("/v1/workbench-runtime/trace-events", s.handleTraceEvents) s.mux.HandleFunc("/v1/workbench-runtime/sessions", s.handleSessions) s.mux.HandleFunc("/v1/workbench-runtime/projection-outbox", s.handleProjectionOutbox) } func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { methodNotAllowed(w, "GET") return } w.Header().Set("content-type", "text/plain; version=0.0.4; charset=utf-8") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(s.cache.PrometheusText(serviceID))) } func (s *Server) handleLive(w http.ResponseWriter, r *http.Request) { cache := s.cache.Diagnostic(r.Context()) if s.config.Cache.UnavailableLivenessFailure && cache["enabled"] == true && cache["available"] != true { writeJSON(w, http.StatusServiceUnavailable, map[string]any{"ok": false, "serviceId": serviceID, "status": "cache-unavailable", "cache": cache, "valuesRedacted": true}) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "serviceId": serviceID, "status": "live", "cache": cache, "valuesRedacted": true}) } func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), s.config.ReadinessDelay) defer cancel() if err := s.db.PingContext(ctx); err != nil { writeAPIError(w, http.StatusServiceUnavailable, "workbench_runtime_db_unavailable", "workbench runtime database is unavailable") return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "serviceId": serviceID, "status": "ready", "cache": s.cache.Diagnostic(r.Context()), "valuesRedacted": true}) } func (s *Server) handleFacts(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, "POST") return } var query factQuery if !decodeJSON(w, r, &query) { return } ctx, cancel := context.WithTimeout(r.Context(), s.config.QueryTimeout) defer cancel() payload, err := s.queryFactsResponse(ctx, query) if err != nil { writeQueryError(w, err) return } writeJSON(w, http.StatusOK, payload) } func (s *Server) queryFactsResponse(ctx context.Context, query factQuery) (map[string]any, error) { plan := s.factsCachePlan(ctx, query) if plan.Key != "" { var entry factsCacheEntry hit, err := s.cache.Get(ctx, plan.Key, &entry) if err != nil { setFactsCacheStatus(plan.Meta, "unavailable") plan.Meta["errorKind"] = classifyCacheError(err) } else if hit && entry.Payload != nil { ageMs, stale := factsCacheEntryAgeMs(entry, time.Now().UTC()) if !stale { payload := factsCacheResponsePayload(entry.Payload, s.persistenceSummary()) setFactsCacheStatus(plan.Meta, "hit") plan.Meta["cacheAgeMs"] = ageMs plan.Meta["dbQueryAvoided"] = true plan.Meta["dbQueryDurationMs"] = int64(0) plan.Meta["payloadBytes"] = entry.PayloadBytes plan.Meta["ttlMs"] = entry.TTLMillis plan.Meta["freshnessSloMs"] = entry.TTLMillis attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } setFactsCacheStatus(plan.Meta, "stale") plan.Meta["cacheAgeMs"] = ageMs plan.Meta["freshnessSloMs"] = entry.TTLMillis } } dbStart := time.Now() facts, err := s.queryFacts(ctx, query) dbDuration := time.Since(dbStart) if err != nil { return nil, err } payload := factsResponsePayload(facts, s.persistenceSummary()) plan.Meta["dbQueryAvoided"] = false plan.Meta["dbQueryDurationMs"] = dbDuration.Milliseconds() if plan.Key == "" { attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } ttl, unstable, ttlReason := factsCacheTTL(plan, s.config.Cache) plan.Meta["unstable"] = unstable plan.Meta["ttlMs"] = ttl.Milliseconds() plan.Meta["freshnessSloMs"] = ttl.Milliseconds() plan.Meta["ttlReason"] = ttlReason storedPayload := factsCacheStorablePayload(payload) payloadBytes, payloadErr := cachePayloadBytes(storedPayload, s.config.Cache.MaxPayloadBytes) if payloadErr != nil { setFactsCacheStatus(plan.Meta, "skipped") plan.Meta["skipReason"] = classifyCacheError(payloadErr) attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } plan.Meta["payloadBytes"] = len(payloadBytes) if ttl <= 0 { setFactsCacheStatus(plan.Meta, "skipped") plan.Meta["skipReason"] = ttlReason attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } entry := factsCacheEntry{CachedAt: time.Now().UTC().Format(time.RFC3339Nano), Payload: storedPayload, PayloadBytes: len(payloadBytes), TTLMillis: ttl.Milliseconds(), ValuesRedacted: true} if err := s.cache.Set(ctx, plan.Key, entry, ttl); err != nil { setFactsCacheStatus(plan.Meta, "set_failed") plan.Meta["errorKind"] = classifyCacheError(err) attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } if plan.Meta["status"] != "stale" { setFactsCacheStatus(plan.Meta, "miss") } plan.Meta["stored"] = true attachFactsCacheMetadata(payload, plan.Meta) s.observeCacheDiagnostic(ctx, plan.Class, plan.Meta) return payload, nil } func (s *Server) observeCacheDiagnostic(ctx context.Context, class string, meta map[string]any) { if s == nil || meta == nil { return } cacheClass := first(text(meta["class"]), class, "unknown") status := first(text(meta["status"]), "unknown") if s.cache != nil { if status == "stale" { s.cache.RecordStale(cacheClass) } if boolValue(meta["dbQueryAvoided"]) { s.cache.RecordDBQueryAvoided(cacheClass) } } attrs := map[string]any{ "cacheStatus": status, "cacheKeyClass": safeCacheToken(cacheClass), "workbench.cache.status": status, "workbench.cache.key_class": safeCacheToken(cacheClass), "workbench.cache.db_avoided": boolValue(meta["dbQueryAvoided"]), "workbench.cache.values_source": "diagnostic", "valuesRedacted": true, } if meta["cacheAgeMs"] != nil { age := int64FromAny(meta["cacheAgeMs"]) attrs["cacheAgeMs"] = age attrs["workbench.cache.age_ms"] = age } if meta["projectionSeq"] != nil { seq := int64FromAny(meta["projectionSeq"]) attrs["projectionSeq"] = seq attrs["workbench.projection.seq"] = seq } if meta["payloadBytes"] != nil { attrs["workbench.cache.payload_bytes"] = int64FromAny(meta["payloadBytes"]) } if meta["ttlMs"] != nil { attrs["workbench.cache.ttl_ms"] = int64FromAny(meta["ttlMs"]) } if meta["freshnessSloMs"] != nil { attrs["workbench.cache.freshness_slo_ms"] = int64FromAny(meta["freshnessSloMs"]) } if revisionSource := text(meta["revisionSource"]); revisionSource != "" { attrs["workbench.cache.revision_source"] = revisionSource } if invalidationMode := text(meta["invalidationMode"]); invalidationMode != "" { attrs["workbench.cache.invalidation_mode"] = invalidationMode } _ = s.withOtelInternalSpan(ctx, "workbench-runtime.cache", attrs, func(context.Context) error { return nil }) } func factsResponsePayload(facts map[string][]any, persistence map[string]any) map[string]any { count := 0 for _, rows := range facts { count += len(rows) } return map[string]any{ "contractVersion": "workbench-runtime-facts-v1", "facts": facts, "count": count, "persistence": persistence, "valuesRedacted": true, } } func (s *Server) factsCachePlan(ctx context.Context, query factQuery) factsCachePlan { class := factsCacheClass(query) meta := factsCacheMetadata(class, "disabled") plan := factsCachePlan{Class: class, Meta: meta, TraceID: strings.TrimSpace(query.TraceID)} if s == nil || s.cache == nil || !s.config.Cache.Enabled || class == "" { return plan } meta["enabled"] = true if plan.TraceID == "" { setFactsCacheStatus(meta, "skipped") meta["skipReason"] = "missing_trace" return plan } state, err := s.traceProjectionState(ctx, plan.TraceID) if err != nil { setFactsCacheStatus(meta, "skipped") meta["skipReason"] = "projection_revision_unavailable" return plan } plan.ProjectionSeq = state.ProjectedSeq plan.ProjectionStatus = state.ProjectionStatus plan.ProjectionHealth = state.ProjectionHealth plan.Status = state.Status meta["projectionSeq"] = state.ProjectedSeq meta["projectionRevision"] = projectionRevisionToken(state.ProjectedSeq) meta["projectionStatus"] = state.ProjectionStatus meta["projectionHealth"] = state.ProjectionHealth meta["traceStatus"] = state.Status meta["revisionSource"] = "workbench_projection_checkpoints" meta["invalidationMode"] = "projection_revision_key" if state.ProjectedSeq <= 0 { setFactsCacheStatus(meta, "skipped") meta["skipReason"] = "missing_projection_revision" return plan } cursor := fmt.Sprintf("after:%d|limit:%d|families:%s", query.AfterProjectedSeq, boundedLimit(query.Limit, 0, 1000), strings.Join(requestedFactFamilies(query), ",")) authority := fmt.Sprintf("contract=workbench-runtime-facts-v1|class=%s|projectionStatus=%s|projectionHealth=%s", class, state.ProjectionStatus, state.ProjectionHealth) key, err := s.cache.BuildKey(cacheKeyParts{Class: class, ActorID: "workbench-runtime", TraceID: plan.TraceID, Cursor: cursor, ProjectionSeq: state.ProjectedSeq, Authority: authority}) if err != nil { setFactsCacheStatus(meta, "skipped") meta["skipReason"] = classifyCacheError(err) return plan } plan.Key = key setFactsCacheStatus(meta, "miss") return plan } type traceProjectionState struct { ProjectedSeq int64 ProjectionStatus string ProjectionHealth string Status string } func (s *Server) traceProjectionState(ctx context.Context, traceID string) (traceProjectionState, error) { facts, err := s.queryFacts(ctx, factQuery{TraceID: traceID, Families: []string{"checkpoints", "turns"}, Limit: 1, TurnsOrder: "updated_desc", CheckpointsOrder: "updated_desc"}) if err != nil { return traceProjectionState{}, err } checkpoint := firstObject(facts["checkpoints"]) turn := firstObject(facts["turns"]) state := traceProjectionState{ ProjectedSeq: int64FromAny(seq(checkpoint)), ProjectionStatus: normalizeProjectionStatus(text(checkpoint["projectionStatus"])), ProjectionHealth: normalizeProjectionHealth(text(checkpoint["projectionHealth"]), normalizeProjectionStatus(text(checkpoint["projectionStatus"]))), Status: normalizeStatus(first(text(checkpoint["status"]), text(turn["status"]))), } if state.ProjectionStatus == "" { state.ProjectionStatus = "unknown" } if state.ProjectionHealth == "" { state.ProjectionHealth = "unknown" } return state, nil } func factsCacheClass(query factQuery) string { if strings.TrimSpace(query.TraceID) == "" { return "" } families := requestedFactFamilies(query) if stringSetEqual(families, []string{"traceEvents"}) { return terminalTracePageCacheClass } if query.AfterProjectedSeq == 0 && stringSetEqual(families, []string{"sessions", "messages", "turns", "checkpoints"}) { return terminalTurnCacheClass } return "" } func requestedFactFamilies(query factQuery) []string { selected := query.Families if len(selected) == 0 { selected = query.FactFamilies } result := []string{} seen := map[string]bool{} for _, family := range selected { family = strings.TrimSpace(family) if family != "" && !seen[family] { result = append(result, family) seen[family] = true } } return result } func stringSetEqual(values []string, expected []string) bool { if len(values) != len(expected) { return false } set := map[string]bool{} for _, value := range values { set[value] = true } for _, value := range expected { if !set[value] { return false } } return true } func factsCacheTTL(plan factsCachePlan, config cacheConfig) (time.Duration, bool, string) { stableProjection := plan.ProjectionStatus == "caught-up" || plan.ProjectionStatus == "blocked" || plan.ProjectionStatus == "stalled" terminal := isTerminal(plan.Status) || stableProjection if !terminal { if config.RunningPageTTL <= 0 { return 0, true, "running_page_ttl_disabled" } return config.RunningPageTTL, true, "running_or_projecting_trace" } if plan.Class == terminalTracePageCacheClass { if config.TerminalTracePageTTL <= 0 { return 0, false, "terminal_trace_page_ttl_disabled" } return config.TerminalTracePageTTL, false, "terminal_trace_page" } if config.TerminalTurnTTL <= 0 { return 0, false, "terminal_turn_ttl_disabled" } return config.TerminalTurnTTL, false, "terminal_turn_snapshot" } func factsCacheMetadata(class string, status string) map[string]any { if class == "" { class = "unknown" } meta := map[string]any{ "class": class, "schemaVersion": cacheKeySchemaVersion, "enabled": false, "status": status, "hit": false, "miss": false, "stale": false, "unavailable": false, "dbQueryAvoided": false, "cacheAgeMs": nil, "dbQueryDurationMs": nil, "payloadBytes": nil, "ttlMs": nil, "freshnessSloMs": nil, "projectionRevision": nil, "invalidationMode": nil, "revisionSource": nil, "valuesRedacted": true, "secretMaterialStored": false, } setFactsCacheStatus(meta, status) return meta } func setFactsCacheStatus(meta map[string]any, status string) { if meta == nil { return } meta["status"] = status meta["hit"] = status == "hit" meta["miss"] = status == "miss" meta["stale"] = status == "stale" meta["unavailable"] = status == "unavailable" || status == "set_failed" } func attachFactsCacheMetadata(payload map[string]any, meta map[string]any) { if payload == nil || meta == nil { return } payload["cache"] = meta } func factsCacheStorablePayload(payload map[string]any) map[string]any { clone := cloneJSONMap(payload) delete(clone, "cache") delete(clone, "persistence") delete(clone, "secretMaterialStored") return objectMap(sanitizeFactsCachePayload(clone)) } func sanitizeFactsCachePayload(value any) any { switch item := value.(type) { case map[string]any: cleaned := map[string]any{} for key, child := range item { if cachePayloadKeyRejected(key) { continue } cleaned[key] = sanitizeFactsCachePayload(child) } return cleaned case []any: cleaned := make([]any, 0, len(item)) for _, child := range item { cleaned = append(cleaned, sanitizeFactsCachePayload(child)) } return cleaned case []map[string]any: cleaned := make([]any, 0, len(item)) for _, child := range item { cleaned = append(cleaned, sanitizeFactsCachePayload(child)) } return cleaned default: return value } } func factsCacheResponsePayload(payload map[string]any, persistence map[string]any) map[string]any { clone := cloneJSONMap(payload) clone["persistence"] = persistence clone["valuesRedacted"] = true return clone } func factsCacheEntryAgeMs(entry factsCacheEntry, now time.Time) (int64, bool) { cachedAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(entry.CachedAt)) if err != nil { return 0, true } age := now.Sub(cachedAt) if age < 0 { age = 0 } if entry.TTLMillis > 0 && age.Milliseconds() > entry.TTLMillis { return age.Milliseconds(), true } return age.Milliseconds(), false } func firstObject(rows []any) map[string]any { if len(rows) == 0 { return map[string]any{} } return objectMap(rows[0]) } func int64FromAny(value any) int64 { switch typed := value.(type) { case int64: return typed case int: return int64(typed) case float64: return int64(typed) case json.Number: parsed, _ := typed.Int64() return parsed default: return 0 } } func (s *Server) handleTraceEvents(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, "POST") return } var query traceEventsQuery if !decodeJSON(w, r, &query) { return } ctx, cancel := context.WithTimeout(r.Context(), s.config.QueryTimeout) defer cancel() events, err := s.queryAgentTraceEvents(ctx, query) if err != nil { writeQueryError(w, err) return } writeJSON(w, http.StatusOK, map[string]any{ "contractVersion": "workbench-runtime-trace-events-v1", "events": events, "count": len(events), "persistence": s.persistenceSummary(), "valuesRedacted": true, }) } func (s *Server) handleProjectionOutbox(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, "POST") return } var query outboxQuery if !decodeJSON(w, r, &query) { return } ctx, cancel := context.WithTimeout(r.Context(), s.config.QueryTimeout) defer cancel() rows, err := s.readProjectionOutbox(ctx, query) if err != nil { writeQueryError(w, err) return } writeJSON(w, http.StatusOK, map[string]any{ "contractVersion": "workbench-runtime-projection-outbox-v1", "rows": rows, "count": len(rows), "persistence": s.persistenceSummary(), "valuesRedacted": true, }) } func (s *Server) handleSessions(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, "POST") return } var query sessionListQuery if !decodeJSON(w, r, &query) { return } ctx, cancel := context.WithTimeout(r.Context(), s.config.QueryTimeout) defer cancel() payload, err := s.listSessions(ctx, query) if err != nil { writeQueryError(w, err) return } writeJSON(w, http.StatusOK, payload) } func (s *Server) listSessions(ctx context.Context, query sessionListQuery) (map[string]any, error) { limit, offset := sessionListPage(query) cacheKey, cacheMeta := s.sessionsSummaryCacheKey(query, limit, offset) if cacheKey != "" { var entry sessionsCacheEntry hit, err := s.cache.Get(ctx, cacheKey, &entry) if err != nil { setSessionsCacheStatus(cacheMeta, "unavailable") cacheMeta["errorKind"] = classifyCacheError(err) } else if hit && entry.Payload != nil { ageMs, stale := sessionsCacheEntryAgeMs(entry, time.Now().UTC()) if !stale { payload := sessionsCacheResponsePayload(entry.Payload, s.persistenceSummary()) setSessionsCacheStatus(cacheMeta, "hit") cacheMeta["cacheAgeMs"] = ageMs cacheMeta["dbQueryAvoided"] = true cacheMeta["dbQueryDurationMs"] = int64(0) cacheMeta["payloadBytes"] = entry.PayloadBytes cacheMeta["ttlMs"] = entry.TTLMillis cacheMeta["freshnessSloMs"] = entry.TTLMillis applySessionsCacheRevisionMetadata(cacheMeta, sessionsSummaryRevision{ProjectionRevision: entry.ProjectionRevision, ProjectionSeqMax: entry.ProjectionSeqMax, SessionUpdatedAtMax: entry.SessionUpdatedAtMax, SessionCount: entry.SessionCount, RevisionSource: "cached_payload"}) attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } setSessionsCacheStatus(cacheMeta, "stale") cacheMeta["cacheAgeMs"] = ageMs cacheMeta["freshnessSloMs"] = entry.TTLMillis applySessionsCacheRevisionMetadata(cacheMeta, sessionsSummaryRevision{ProjectionRevision: entry.ProjectionRevision, ProjectionSeqMax: entry.ProjectionSeqMax, SessionUpdatedAtMax: entry.SessionUpdatedAtMax, SessionCount: entry.SessionCount, RevisionSource: "cached_payload"}) } } dbStart := time.Now() payload, err := s.listSessionsFromDB(ctx, query, limit, offset) dbDuration := time.Since(dbStart) if err != nil { return nil, err } cacheMeta["dbQueryAvoided"] = false cacheMeta["dbQueryDurationMs"] = dbDuration.Milliseconds() if cacheKey == "" { attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } ttl, unstable, ttlReason := sessionsSummaryCacheTTL(payload, s.config.Cache) cacheMeta["unstable"] = unstable cacheMeta["ttlMs"] = ttl.Milliseconds() cacheMeta["freshnessSloMs"] = ttl.Milliseconds() cacheMeta["ttlReason"] = ttlReason revision := sessionsSummaryRevisionForPayload(payload) applySessionsCacheRevisionMetadata(cacheMeta, revision) storedPayload := sessionsCacheStorablePayload(payload) payloadBytes, payloadErr := cachePayloadBytes(storedPayload, s.config.Cache.MaxPayloadBytes) if payloadErr != nil { setSessionsCacheStatus(cacheMeta, "skipped") cacheMeta["skipReason"] = classifyCacheError(payloadErr) attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } cacheMeta["payloadBytes"] = len(payloadBytes) if ttl <= 0 { setSessionsCacheStatus(cacheMeta, "skipped") cacheMeta["skipReason"] = ttlReason attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } entry := sessionsCacheEntry{CachedAt: time.Now().UTC().Format(time.RFC3339Nano), Payload: storedPayload, PayloadBytes: len(payloadBytes), TTLMillis: ttl.Milliseconds(), ProjectionRevision: revision.ProjectionRevision, ProjectionSeqMax: revision.ProjectionSeqMax, SessionUpdatedAtMax: revision.SessionUpdatedAtMax, SessionCount: revision.SessionCount, ValuesRedacted: true} if err := s.cache.Set(ctx, cacheKey, entry, ttl); err != nil { setSessionsCacheStatus(cacheMeta, "set_failed") cacheMeta["errorKind"] = classifyCacheError(err) attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } if cacheMeta["status"] != "stale" { setSessionsCacheStatus(cacheMeta, "miss") } cacheMeta["stored"] = true attachSessionsCacheMetadata(payload, cacheMeta) s.observeCacheDiagnostic(ctx, sessionsSummaryCacheClass, cacheMeta) return payload, nil } func (s *Server) listSessionsFromDB(ctx context.Context, query sessionListQuery, limit int, offset int) (map[string]any, error) { ownerUserID := strings.TrimSpace(query.Actor.ID) pageFacts, err := s.queryFacts(ctx, factQuery{OwnerUserID: ownerUserID, Limit: limit + offset + 1, Families: []string{"sessions"}, SessionsOrder: "updated_desc", SessionProjection: "summary"}) if err != nil { return nil, err } natural := visibleSessions(pageFacts["sessions"], query.Actor) if offset > 0 { if offset >= len(natural) { natural = []map[string]any{} } else { natural = natural[offset:] } } if len(natural) > limit+1 { natural = natural[:limit+1] } includeID := strings.TrimSpace(query.IncludeSessionID) if includeID != "" && !sessionListContainsRoute(natural, includeID) { includeFacts, err := s.queryIncludedSession(ctx, includeID) if err != nil { return nil, err } included := visibleSessions(includeFacts["sessions"], query.Actor) if len(included) > 0 { pageFacts = mergeFacts(pageFacts, includeFacts) natural = append([]map[string]any{included[0]}, natural...) } } hasMore := len(natural) > limit pageSessions := natural if len(pageSessions) > limit { pageSessions = pageSessions[:limit] } summaryFacts, err := s.querySummaryFacts(ctx, pageSessions) if err != nil { return nil, err } facts := mergeFacts(pageFacts, summaryFacts) summaries := []any{} for _, session := range pageSessions { if summary := sessionSummary(session, facts); summary != nil { summaries = append(summaries, summary) } } return map[string]any{"ok": true, "status": "succeeded", "contractVersion": "workbench-sessions-v1", "sessions": summaries, "count": len(summaries), "cursor": cursorOrNil(offset), "hasMore": hasMore, "nextCursor": nextCursor(offset, limit, hasMore), "persistence": s.persistenceSummary(), "servedBy": serviceID, "valuesRedacted": true, "secretMaterialStored": false}, nil } func sessionListPage(query sessionListQuery) (int, int) { limit := boundedLimit(query.Limit, 20, 100) offset := query.Offset if offset <= 0 { offset = cursorOffset(query.Cursor) } if offset < 0 { offset = 0 } return limit, offset } func (s *Server) sessionsSummaryCacheKey(query sessionListQuery, limit int, offset int) (string, map[string]any) { meta := sessionsCacheMetadata("disabled") if s == nil || s.cache == nil || !s.config.Cache.Enabled { return "", meta } meta["enabled"] = true actorID := strings.TrimSpace(query.Actor.ID) if actorID == "" { setSessionsCacheStatus(meta, "skipped") meta["skipReason"] = "missing_actor" return "", meta } includeID := strings.TrimSpace(query.IncludeSessionID) actorRole := strings.TrimSpace(query.Actor.Role) cursor := fmt.Sprintf("idx:%d|limit:%d|include:%s|role:%s", offset, limit, includeID, actorRole) authority := fmt.Sprintf("contract=workbench-sessions-v1|limit=%d|offset=%d|include=%t|role=%s", limit, offset, includeID != "", actorRole) key, err := s.cache.BuildKey(cacheKeyParts{Class: sessionsSummaryCacheClass, ActorID: actorID, SessionID: includeID, Cursor: cursor, ProjectionRevision: "workbench-sessions-v1", Authority: authority}) if err != nil { setSessionsCacheStatus(meta, "skipped") meta["skipReason"] = classifyCacheError(err) return "", meta } setSessionsCacheStatus(meta, "miss") return key, meta } func sessionsCacheMetadata(status string) map[string]any { meta := map[string]any{ "class": sessionsSummaryCacheClass, "schemaVersion": cacheKeySchemaVersion, "enabled": false, "status": status, "hit": false, "miss": false, "stale": false, "unavailable": false, "dbQueryAvoided": false, "cacheAgeMs": nil, "dbQueryDurationMs": nil, "payloadBytes": nil, "ttlMs": nil, "freshnessSloMs": nil, "projectionRevision": nil, "projectionSeq": nil, "sessionUpdatedAtMax": nil, "sessionCount": nil, "revisionSource": nil, "invalidationMode": "ttl_slo", "valuesRedacted": true, "secretMaterialStored": false, } setSessionsCacheStatus(meta, status) return meta } func setSessionsCacheStatus(meta map[string]any, status string) { if meta == nil { return } meta["status"] = status meta["hit"] = status == "hit" meta["miss"] = status == "miss" meta["stale"] = status == "stale" meta["unavailable"] = status == "unavailable" || status == "set_failed" } func attachSessionsCacheMetadata(payload map[string]any, meta map[string]any) { if payload == nil || meta == nil { return } payload["cache"] = meta } func sessionsSummaryCacheTTL(payload map[string]any, config cacheConfig) (time.Duration, bool, string) { if sessionsSummaryPayloadUnstable(payload) { if config.RunningPageTTL <= 0 { return 0, true, "running_page_ttl_disabled" } return config.RunningPageTTL, true, "running_or_projecting_page" } if config.SessionsSummaryTTL <= 0 { return 0, false, "sessions_summary_ttl_disabled" } return config.SessionsSummaryTTL, false, "stable_page" } func sessionsSummaryRevisionForPayload(payload map[string]any) sessionsSummaryRevision { rows := sessionsPayloadRows(payload["sessions"]) revision := sessionsSummaryRevision{RevisionSource: "response_payload"} maxUpdatedAt := "" var maxSeq int64 for _, row := range rows { item := objectMap(row) if len(item) == 0 { continue } revision.SessionCount++ if updatedAt := text(item["updatedAt"]); updatedAt > maxUpdatedAt { maxUpdatedAt = updatedAt } maxSeq = maxInt64(maxSeq, int64FromAny(item["projectedSeq"])) projection := objectMap(item["projection"]) maxSeq = maxInt64(maxSeq, int64FromAny(projection["lastProjectedSeq"])) turnSummary := objectMap(item["turnSummary"]) maxSeq = maxInt64(maxSeq, int64FromAny(turnSummary["eventCount"])) } revision.ProjectionSeqMax = maxSeq revision.SessionUpdatedAtMax = maxUpdatedAt raw := fmt.Sprintf("contract=workbench-sessions-v1|count=%d|maxProjectionSeq=%d|maxUpdatedAt=%s", revision.SessionCount, revision.ProjectionSeqMax, revision.SessionUpdatedAtMax) revision.ProjectionRevision = "digest:" + cacheHash(raw) return revision } func applySessionsCacheRevisionMetadata(meta map[string]any, revision sessionsSummaryRevision) { if meta == nil { return } if revision.ProjectionRevision != "" { meta["projectionRevision"] = revision.ProjectionRevision } meta["projectionSeq"] = revision.ProjectionSeqMax meta["sessionUpdatedAtMax"] = nilIfEmpty(revision.SessionUpdatedAtMax) meta["sessionCount"] = revision.SessionCount meta["revisionSource"] = first(revision.RevisionSource, "response_payload") meta["invalidationMode"] = "ttl_slo" } func projectionRevisionToken(projectedSeq int64) string { if projectedSeq <= 0 { return "" } return "seq:" + strconv.FormatInt(projectedSeq, 10) } func maxInt64(a int64, b int64) int64 { if b > a { return b } return a } func nilIfEmpty(value string) any { if strings.TrimSpace(value) == "" { return nil } return value } func sessionsSummaryPayloadUnstable(payload map[string]any) bool { for _, row := range sessionsPayloadRows(payload["sessions"]) { item := objectMap(row) if len(item) == 0 { continue } if boolValue(item["running"]) || !boolValue(item["terminal"]) { return true } if status := normalizeProjectionStatus(text(item["projectionStatus"])); status != "" && status != "caught-up" { return true } turnSummary := objectMap(item["turnSummary"]) if len(turnSummary) > 0 && (boolValue(turnSummary["running"]) || !boolValue(turnSummary["terminal"])) { return true } } return false } func sessionsPayloadRows(value any) []any { switch rows := value.(type) { case []any: return rows case []map[string]any: out := make([]any, 0, len(rows)) for _, row := range rows { out = append(out, row) } return out default: return []any{} } } func sessionsCacheStorablePayload(payload map[string]any) map[string]any { clone := cloneJSONMap(payload) delete(clone, "cache") delete(clone, "persistence") delete(clone, "secretMaterialStored") return clone } func sessionsCacheResponsePayload(payload map[string]any, persistence map[string]any) map[string]any { clone := cloneJSONMap(payload) clone["persistence"] = persistence clone["secretMaterialStored"] = false clone["valuesRedacted"] = true clone["servedBy"] = serviceID return clone } func sessionsCacheEntryAgeMs(entry sessionsCacheEntry, now time.Time) (int64, bool) { cachedAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(entry.CachedAt)) if err != nil { return 0, true } age := now.Sub(cachedAt) if age < 0 { age = 0 } if entry.TTLMillis > 0 && age.Milliseconds() > entry.TTLMillis { return age.Milliseconds(), true } return age.Milliseconds(), false } func cloneJSONMap(value map[string]any) map[string]any { if value == nil { return map[string]any{} } payload, err := json.Marshal(value) if err != nil { return map[string]any{} } var clone map[string]any if err := json.Unmarshal(payload, &clone); err != nil || clone == nil { return map[string]any{} } return clone } func (s *Server) queryIncludedSession(ctx context.Context, routeID string) (map[string][]any, error) { if strings.HasPrefix(routeID, "ses_") { return s.queryFacts(ctx, factQuery{SessionID: routeID, Limit: 1, Families: []string{"sessions"}, SessionProjection: "summary"}) } return s.queryFacts(ctx, factQuery{ConversationID: routeID, Limit: 1, Families: []string{"sessions"}, SessionProjection: "summary"}) } func (s *Server) querySummaryFacts(ctx context.Context, sessions []map[string]any) (map[string][]any, error) { sessionIDs := uniqueStrings(mapSessions(sessions, sessionID)) traceIDs := uniqueStrings(mapSessions(sessions, lastTraceID)) facts := emptyFacts() if len(sessionIDs) > 0 { messageSummaries, err := s.queryMessageSummaries(ctx, sessionIDs) if err != nil { return nil, err } facts["messageSummaries"] = messageSummaries } if len(traceIDs) > 0 { byTrace, err := s.queryFacts(ctx, factQuery{TraceIDs: traceIDs, Families: []string{"turns", "checkpoints"}, Limit: len(traceIDs), TurnsOrder: "updated_desc", CheckpointsOrder: "updated_desc"}) if err != nil { return nil, err } facts = mergeFacts(facts, byTrace) } return facts, nil } func (s *Server) queryFacts(ctx context.Context, query factQuery) (map[string][]any, error) { families := factFamilySet(query) facts := emptyFacts() var err error if families["sessions"] { facts["sessions"], err = s.queryFactRows(ctx, "workbench_sessions", "session_json", query, []fieldColumn{ {"sessionId", query.SessionIDs, "session_id"}, {"traceId", query.TraceIDs, "last_trace_id"}, {"ownerUserId", query.OwnerUserIDs, "owner_user_id"}, {"conversationId", query.ConversationIDs, "conversation_id"}, {"status", query.Statuses, "status"}, }) if err != nil { return nil, err } } if families["messages"] { facts["messages"], err = s.queryFactRows(ctx, "workbench_messages", "message_json", query, []fieldColumn{ {"messageId", query.MessageIDs, "message_id"}, {"sessionId", query.SessionIDs, "session_id"}, {"traceId", query.TraceIDs, "trace_id"}, {"turnId", query.TurnIDs, "turn_id"}, {"status", query.Statuses, "status"}, }) if err != nil { return nil, err } } if families["parts"] { facts["parts"], err = s.queryFactRows(ctx, "workbench_parts", "part_json", query, []fieldColumn{ {"messageId", query.MessageIDs, "message_id"}, {"sessionId", query.SessionIDs, "session_id"}, {"traceId", query.TraceIDs, "trace_id"}, {"turnId", query.TurnIDs, "turn_id"}, {"status", query.Statuses, "status"}, }) if err != nil { return nil, err } } if families["turns"] { facts["turns"], err = s.queryFactRows(ctx, "workbench_turns", "turn_json", query, []fieldColumn{ {"turnId", query.TurnIDs, "turn_id"}, {"sessionId", query.SessionIDs, "session_id"}, {"traceId", query.TraceIDs, "trace_id"}, {"messageId", query.MessageIDs, "message_id"}, {"status", query.Statuses, "status"}, }) if err != nil { return nil, err } } if families["traceEvents"] { facts["traceEvents"], err = s.queryFactRows(ctx, "workbench_trace_events", "event_json", query, []fieldColumn{ {"traceId", query.TraceIDs, "trace_id"}, {"sessionId", query.SessionIDs, "session_id"}, {"turnId", query.TurnIDs, "turn_id"}, {"messageId", query.MessageIDs, "message_id"}, {"eventType", query.EventTypes, "event_type"}, }) if err != nil { return nil, err } } if families["checkpoints"] { facts["checkpoints"], err = s.queryFactRows(ctx, "workbench_projection_checkpoints", "checkpoint_json", query, []fieldColumn{ {"traceId", query.TraceIDs, "trace_id"}, {"sessionId", query.SessionIDs, "session_id"}, {"turnId", query.TurnIDs, "turn_id"}, {"runId", query.RunIDs, "run_id"}, {"commandId", query.CommandIDs, "command_id"}, {"projectionStatus", query.ProjectionStatuses, "projection_status"}, {"projectionHealth", query.ProjectionHealths, "projection_health"}, }) if err != nil { return nil, err } } return facts, nil } func (s *Server) queryFactRows(ctx context.Context, table string, jsonColumn string, query factQuery, fields []fieldColumn) ([]any, error) { clauses, args := factClauses(query, fields) limit := boundedLimit(query.Limit, 0, 1000) family := factFamilyForTable(table) stage := "workbench_runtime.db." + family if table == "workbench_trace_events" { if query.AfterProjectedSeq > 0 { args = append(args, query.AfterProjectedSeq) clauses = append(clauses, fmt.Sprintf("projected_seq > $%d", len(args))) } sqlText := fmt.Sprintf("SELECT %s FROM %s%s ORDER BY projected_seq ASC", jsonColumn, table, whereSQL(clauses)) if limit > 0 { args = append(args, limit) sqlText += fmt.Sprintf(" LIMIT $%d", len(args)) } return s.queryJSONColumn(ctx, stage, table, sqlText, args) } orderDirection := "ASC" if factOrder(query, family) == "updated_desc" { orderDirection = "DESC" } if table == "workbench_sessions" && query.SessionProjection == "summary" { stage = "workbench_runtime.db.sessions_summary" sqlText := fmt.Sprintf("SELECT %s FROM workbench_sessions%s ORDER BY updated_at %s", sessionSummarySelectClause(), whereSQL(clauses), orderDirection) if limit > 0 { args = append(args, limit) sqlText += fmt.Sprintf(" LIMIT $%d", len(args)) } return s.querySessionSummaries(ctx, stage, sqlText, args) } sqlText := fmt.Sprintf("SELECT %s FROM %s%s ORDER BY updated_at %s", jsonColumn, table, whereSQL(clauses), orderDirection) if limit > 0 { args = append(args, limit) sqlText += fmt.Sprintf(" LIMIT $%d", len(args)) } return s.queryJSONColumn(ctx, stage, table, sqlText, args) } func (s *Server) queryJSONColumn(ctx context.Context, stage string, table string, sqlText string, args []any) ([]any, error) { result := []any{} err := s.withOtelInternalSpan(ctx, stage, s.dbQueryAttrs(table, len(args), nil), func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, sqlText, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var raw sql.NullString if err := rows.Scan(&raw); err != nil { return err } var item any if raw.Valid && strings.TrimSpace(raw.String) != "" && json.Unmarshal([]byte(raw.String), &item) == nil && item != nil { result = append(result, item) } } return rows.Err() }) if err != nil { return nil, wrapQueryStage(stage, err) } return result, nil } func (s *Server) dbQueryAttrs(table string, argCount int, extra map[string]any) map[string]any { stats := s.db.Stats() attrs := map[string]any{ "db.system": "postgresql", "db.operation.name": "SELECT", "db.sql.table": table, "db.query.arg_count": int64(argCount), "db.pool.max_open": int64(s.config.PoolMax), "db.pool.open_connections": int64(stats.OpenConnections), "db.pool.in_use": int64(stats.InUse), "db.pool.idle": int64(stats.Idle), "db.pool.wait_count": int64(stats.WaitCount), "db.pool.wait_duration_ms": stats.WaitDuration.Milliseconds(), "db.pool.max_idle_closed": int64(stats.MaxIdleClosed), "db.pool.max_lifetime_closed": int64(stats.MaxLifetimeClosed), } for key, value := range extra { attrs[key] = value } return attrs } func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlText string, args []any) ([]any, error) { var lastErr error for attempt := 1; attempt <= sessionSummaryQueryMaxAttempts; attempt++ { attemptResult, err := s.querySessionSummariesAttempt(ctx, stage, sqlText, args, attempt, sessionSummaryQueryMaxAttempts) if err == nil { return attemptResult, nil } lastErr = err if attempt == sessionSummaryQueryMaxAttempts || !retryableSessionSummaryQueryError(err) || ctx.Err() != nil { break } backoff := sessionSummaryRetryBackoff(attempt) timer := time.NewTimer(backoff) select { case <-ctx.Done(): timer.Stop() return nil, wrapQueryStage(stage, ctx.Err()) case <-timer.C: } } return nil, wrapQueryStage(stage, lastErr) } func (s *Server) querySessionSummariesAttempt(ctx context.Context, stage string, sqlText string, args []any, attempt int, maxAttempts int) ([]any, error) { result := []any{} attemptCtx, cancel := context.WithTimeout(ctx, sessionSummaryQueryAttemptTimeout) defer cancel() attrs := s.dbQueryAttrs("workbench_sessions", len(args), map[string]any{ "db.index.expected": "idx_workbench_sessions_owner_updated|idx_workbench_sessions_updated", "retryAttempt": int64(attempt), "retryMax": int64(maxAttempts), }) if attempt > 1 { attrs["retryBackoffMs"] = sessionSummaryRetryBackoff(attempt - 1).Milliseconds() } err := s.withOtelInternalSpan(attemptCtx, stage, attrs, func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, sqlText, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var sessionID, ownerUserID, projectID, conversationID, threadID, status, lastTraceID sql.NullString var projectedSeq sql.NullInt64 var createdAt, updatedAt sql.NullString if err := rows.Scan(&sessionID, &ownerUserID, &projectID, &conversationID, &threadID, &status, &lastTraceID, &projectedSeq, &createdAt, &updatedAt); err != nil { return err } if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" { continue } item := map[string]any{ "id": sessionID.String, "sessionId": sessionID.String, "ownerUserId": nullableString(ownerUserID), "projectId": nullableString(projectID), "conversationId": nullableString(conversationID), "threadId": nullableString(threadID), "status": first(nullableStringValue(status), "unknown"), "lastTraceId": nullableString(lastTraceID), "projectedSeq": nullableInt(projectedSeq), "createdAt": nullableTimeText(createdAt), "updatedAt": first(nullableTimeText(updatedAt), nullableTimeText(createdAt)), "valuesPrinted": false, "valuesRedacted": true, } result = append(result, item) } return rows.Err() }) return result, err } func sessionSummaryRetryBackoff(attempt int) time.Duration { if attempt <= 1 { return sessionSummaryQueryRetryBackoff } return time.Duration(attempt) * sessionSummaryQueryRetryBackoff } func retryableSessionSummaryQueryError(err error) bool { if err == nil { return false } if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { return true } switch sqlErrorCode(err) { case "40001", "40P01", "55P03", "57014", "query_timeout": return true default: return false } } func (s *Server) queryMessageSummaries(ctx context.Context, sessionIDs []string) ([]any, error) { cleaned := uniqueStrings(sessionIDs) if len(cleaned) == 0 { return []any{}, nil } args := []any{} placeholders := []string{} for _, sid := range cleaned { args = append(args, sid) placeholders = append(placeholders, fmt.Sprintf("$%d", len(args))) } bySession := map[string]map[string]any{} countSQL := fmt.Sprintf("SELECT session_id, COUNT(*) AS message_count FROM workbench_messages WHERE session_id IN (%s) GROUP BY session_id", strings.Join(placeholders, ", ")) countStage := "workbench_runtime.db.message_counts" err := s.withOtelInternalSpan(ctx, countStage, s.dbQueryAttrs("workbench_messages", len(args), map[string]any{"db.index.expected": "idx_workbench_messages_session_updated"}), func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, countSQL, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var sessionID sql.NullString var messageCount sql.NullInt64 if err := rows.Scan(&sessionID, &messageCount); err != nil { return err } if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" { continue } item := map[string]any{"sessionId": sessionID.String, "messageCount": nullableInt(messageCount), "valuesRedacted": true} bySession[sessionID.String] = item } return rows.Err() }) if err != nil { return nil, wrapQueryStage(countStage, err) } firstUserSQL := fmt.Sprintf("SELECT DISTINCT ON (session_id) session_id, message_json FROM workbench_messages WHERE session_id IN (%s) AND role = 'user' ORDER BY session_id ASC, updated_at ASC, message_id ASC", strings.Join(placeholders, ", ")) firstUserStage := "workbench_runtime.db.first_user_messages" err = s.withOtelInternalSpan(ctx, firstUserStage, s.dbQueryAttrs("workbench_messages", len(args), map[string]any{"db.index.expected": "idx_workbench_messages_session_role_updated"}), func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, firstUserSQL, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var sessionID, firstUserMessageJSON sql.NullString if err := rows.Scan(&sessionID, &firstUserMessageJSON); err != nil { return err } if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" { continue } item := bySession[sessionID.String] if item == nil { item = map[string]any{"sessionId": sessionID.String, "messageCount": 0, "valuesRedacted": true} bySession[sessionID.String] = item } if firstUserMessageJSON.Valid && strings.TrimSpace(firstUserMessageJSON.String) != "" { var message map[string]any if json.Unmarshal([]byte(firstUserMessageJSON.String), &message) == nil && len(message) > 0 { item["firstUserMessagePreview"] = firstUserPreview([]map[string]any{message}) } } } return rows.Err() }) if err != nil { return nil, wrapQueryStage(firstUserStage, err) } result := []any{} for _, sid := range cleaned { if item := bySession[sid]; item != nil { result = append(result, item) } } return result, nil } func (s *Server) queryAgentTraceEvents(ctx context.Context, query traceEventsQuery) ([]any, error) { clauses := []string{} args := []any{} addTextClause(&clauses, &args, "trace_id", query.TraceID) addTextClause(&clauses, &args, "agent_session_id", query.SessionID) addTextClause(&clauses, &args, "worker_session_id", query.WorkerSessionID) addTextClause(&clauses, &args, "level", query.Level) sqlText := fmt.Sprintf("SELECT event_json FROM agent_trace_events%s ORDER BY occurred_at ASC, id ASC", whereSQL(clauses)) if limit := boundedLimit(query.Limit, 0, 1000); limit > 0 { args = append(args, limit) sqlText += fmt.Sprintf(" LIMIT $%d", len(args)) } return s.queryJSONColumn(ctx, "workbench_runtime.db.agent_trace_events", "agent_trace_events", sqlText, args) } func (s *Server) readProjectionOutbox(ctx context.Context, query outboxQuery) ([]any, error) { limit := boundedLimit(query.Limit, 100, 500) args := []any{query.AfterSeq} clauses := []string{"outbox_seq > $1"} addTextClause(&clauses, &args, "trace_id", query.TraceID) addTextClause(&clauses, &args, "session_id", query.SessionID) args = append(args, limit) sqlText := fmt.Sprintf("SELECT outbox_seq, trace_id, session_id, turn_id, message_id, projected_seq, source_seq, source_event_id, commit_type, terminal, sealed, payload_json, created_at FROM workbench_projection_outbox WHERE %s ORDER BY outbox_seq ASC LIMIT $%d", strings.Join(clauses, " AND "), len(args)) stage := "workbench_runtime.db.projection_outbox" result := []any{} err := s.withOtelInternalSpan(ctx, stage, map[string]any{ "db.system": "postgresql", "db.operation.name": "SELECT", "db.sql.table": "workbench_projection_outbox", "db.query.arg_count": int64(len(args)), }, func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, sqlText, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var outboxSeq, projectedSeq, sourceSeq sql.NullInt64 var traceID, sessionID, turnID, messageID, sourceEventID, commitType sql.NullString var terminal, sealed sql.NullBool var raw sql.NullString var createdAt sql.NullTime if err := rows.Scan(&outboxSeq, &traceID, &sessionID, &turnID, &messageID, &projectedSeq, &sourceSeq, &sourceEventID, &commitType, &terminal, &sealed, &raw, &createdAt); err != nil { return err } var payload any if raw.Valid && strings.TrimSpace(raw.String) != "" { _ = json.Unmarshal([]byte(raw.String), &payload) } result = append(result, map[string]any{ "outboxSeq": nullableInt(outboxSeq), "traceId": nullableString(traceID), "sessionId": nullableString(sessionID), "turnId": nullableString(turnID), "messageId": nullableString(messageID), "projectedSeq": nullableInt(projectedSeq), "sourceSeq": nullableInt(sourceSeq), "sourceEventId": nullableString(sourceEventID), "commitType": nullableString(commitType), "terminal": nullableBool(terminal), "sealed": nullableBool(sealed), "payload": payload, "createdAt": nullableTime(createdAt), "valuesPrinted": false, }) } return rows.Err() }) if err != nil { return nil, wrapQueryStage(stage, err) } return result, nil } func factClauses(query factQuery, fields []fieldColumn) ([]string, []any) { clauses := []string{} args := []any{} for _, field := range fields { value := singularField(query, field.field) addTextClause(&clauses, &args, field.column, value) addTextListClause(&clauses, &args, field.column, field.values) } return clauses, args } func addTextClause(clauses *[]string, args *[]any, column string, value string) { text := strings.TrimSpace(value) if text == "" { return } *args = append(*args, text) *clauses = append(*clauses, fmt.Sprintf("%s = $%d", column, len(*args))) } func addTextListClause(clauses *[]string, args *[]any, column string, values []string) { cleaned := []string{} for _, value := range values { if text := strings.TrimSpace(value); text != "" { cleaned = append(cleaned, text) } } if len(cleaned) == 0 { return } placeholders := []string{} for _, value := range cleaned { *args = append(*args, value) placeholders = append(placeholders, fmt.Sprintf("$%d", len(*args))) } *clauses = append(*clauses, fmt.Sprintf("%s IN (%s)", column, strings.Join(placeholders, ", "))) } func singularField(query factQuery, field string) string { switch field { case "sessionId": return query.SessionID case "traceId": return query.TraceID case "messageId": return query.MessageID case "turnId": return query.TurnID case "status": return query.Status case "ownerUserId": return query.OwnerUserID case "conversationId": return query.ConversationID case "eventType": return query.EventType case "projectionStatus": return query.ProjectionStatus case "projectionHealth": return query.ProjectionHealth case "runId": return query.RunID case "commandId": return query.CommandID default: return "" } } func whereSQL(clauses []string) string { if len(clauses) == 0 { return "" } return " WHERE " + strings.Join(clauses, " AND ") } func emptyFacts() map[string][]any { return map[string][]any{ "sessions": []any{}, "messageSummaries": []any{}, "messages": []any{}, "parts": []any{}, "turns": []any{}, "traceEvents": []any{}, "checkpoints": []any{}, } } func factFamilySet(query factQuery) map[string]bool { all := []string{"sessions", "messages", "parts", "turns", "traceEvents", "checkpoints"} selected := query.Families if len(selected) == 0 { selected = query.FactFamilies } result := map[string]bool{} if len(selected) == 0 { for _, family := range all { result[family] = true } return result } valid := map[string]bool{} for _, family := range all { valid[family] = true } for _, family := range selected { family = strings.TrimSpace(family) if valid[family] { result[family] = true } } if len(result) == 0 { for _, family := range all { result[family] = true } } return result } func factFamilyForTable(table string) string { switch table { case "workbench_sessions": return "sessions" case "workbench_messages": return "messages" case "workbench_parts": return "parts" case "workbench_turns": return "turns" case "workbench_trace_events": return "traceEvents" case "workbench_projection_checkpoints": return "checkpoints" default: return "unknown" } } func factOrder(query factQuery, family string) string { value := query.Order switch family { case "sessions": if query.SessionsOrder != "" { value = query.SessionsOrder } case "messages": if query.MessagesOrder != "" { value = query.MessagesOrder } case "parts": if query.PartsOrder != "" { value = query.PartsOrder } case "turns": if query.TurnsOrder != "" { value = query.TurnsOrder } case "checkpoints": if query.CheckpointsOrder != "" { value = query.CheckpointsOrder } } if value == "updated_desc" { return "updated_desc" } return "updated_asc" } func sessionSummarySelectClause() string { return strings.Join([]string{ "session_id", "owner_user_id", "project_id", "conversation_id", "thread_id", "status", "last_trace_id", "projected_seq", "created_at", "updated_at", }, ", ") } func providerProfileFromSessionJSON(raw sql.NullString) string { if !raw.Valid || strings.TrimSpace(raw.String) == "" { return "" } var payload map[string]any if err := json.Unmarshal([]byte(raw.String), &payload); err != nil { return "" } if value := strings.TrimSpace(text(payload["providerProfile"])); value != "" { return value } if value := strings.TrimSpace(text(objectMap(payload["sessionJson"])["providerProfile"])); value != "" { return value } return "" } func boundedLimit(value int, fallback int, max int) int { if value <= 0 { return fallback } if max > 0 && value > max { return max } return value } func visibleSessions(rows []any, actor sessionActor) []map[string]any { result := []map[string]any{} for _, row := range rows { item := objectMap(row) if len(item) == 0 { continue } if strings.ToLower(text(item["status"])) == "archived" { continue } if text(item["ownerUserId"]) != strings.TrimSpace(actor.ID) { continue } result = append(result, item) } return result } func sessionSummary(session map[string]any, facts map[string][]any) map[string]any { sid := sessionID(session) if sid == "" { return nil } traceID := first(lastTraceID(session), latestTraceIDForSession(facts, sid)) var projection map[string]any if traceID != "" { projection = projectionForTrace(facts, traceID) } turn := turnForTrace(facts, traceID) checkpoint := checkpointForTrace(facts, traceID) messages := messagesForSession(facts, sid) messageSummary := messageSummaryForSession(facts, sid) messageCount := len(messages) firstUserMessagePreview := firstUserPreview(messages) if len(messageSummary) > 0 { messageCount = intFromAny(messageSummary["messageCount"], messageCount) if preview := messageSummary["firstUserMessagePreview"]; preview != nil { firstUserMessagePreview = preview } } checkpointStatus := terminalStatus(text(checkpoint["status"])) turnStatus := normalizeStatus(turn["status"]) status := normalizeStatus(first(checkpointStatus, turnStatus, text(session["status"]))) timingSource := turn if checkpointStatus != "" { timingSource = checkpoint } timing := timingProjection(timingSource, status) var turnSummary any if len(turn) > 0 { turnSummary = map[string]any{"turnId": first(text(turn["turnId"]), traceID), "traceId": traceID, "status": status, "running": isRunning(status), "terminal": isTerminal(status), "eventCount": projection["lastProjectedSeq"], "projection": projection, "projectionStatus": projection["projectionStatus"], "projectionHealth": projection["projectionHealth"], "staleMs": projection["staleMs"], "blocker": projection["blocker"], "timing": timing, "startedAt": timing["startedAt"], "lastEventAt": timing["lastEventAt"], "finishedAt": timing["finishedAt"], "durationMs": timing["durationMs"], "updatedAt": updatedAt(turn)} } return map[string]any{"sessionId": sid, "threadId": nullableText(session["threadId"]), "agentId": first(text(session["agentId"]), "hwlab-code-agent"), "status": status, "running": isRunning(status), "terminal": isTerminal(status), "lastTraceId": nullableText(traceID), "projection": projection, "projectionStatus": projection["projectionStatus"], "projectionHealth": projection["projectionHealth"], "staleMs": projection["staleMs"], "blocker": projection["blocker"], "providerProfile": nullableText(first(text(session["providerProfile"]), text(objectMap(session["sessionJson"])["providerProfile"]))), "messageCount": messageCount, "firstUserMessagePreview": firstUserMessagePreview, "updatedAt": updatedAt(session), "turnSummary": turnSummary, "valuesRedacted": true} } func messageSummaryForSession(facts map[string][]any, sid string) map[string]any { for _, row := range facts["messageSummaries"] { item := objectMap(row) if text(item["sessionId"]) == sid { return item } } return map[string]any{} } func intFromAny(value any, fallback int) int { switch typed := value.(type) { case int: return typed case int64: return int(typed) case float64: return int(typed) case json.Number: if parsed, err := typed.Int64(); err == nil { return int(parsed) } } return fallback } func messagesForSession(facts map[string][]any, sid string) []map[string]any { result := []map[string]any{} for _, row := range facts["messages"] { item := objectMap(row) if text(item["sessionId"]) == sid { result = append(result, item) } } return result } func firstUserPreview(messages []map[string]any) any { for _, message := range messages { if text(message["role"]) != "user" { continue } if preview := text(message["textPreview"]); preview != "" { return preview } body := first(text(message["text"]), text(message["content"]), text(message["message"])) if body == "" { return nil } if len(body) > 240 { return body[:240] } return body } return nil } func projectionForTrace(facts map[string][]any, traceID string) map[string]any { checkpoint := checkpointForTrace(facts, traceID) if len(checkpoint) == 0 { return map[string]any{"projectionStatus": "unknown", "projectionHealth": "unknown", "lastProjectedSeq": nil, "sourceRunId": nil, "sourceCommandId": nil, "staleMs": nil, "blocker": nil, "updatedAt": nil, "valuesRedacted": true} } status := normalizeProjectionStatus(text(checkpoint["projectionStatus"])) health := normalizeProjectionHealth(text(checkpoint["projectionHealth"]), status) timing := timingProjection(checkpoint, normalizeStatus(checkpoint["status"])) return map[string]any{"projectionStatus": status, "projectionHealth": health, "lastProjectedSeq": seq(checkpoint), "sourceRunId": nullableText(first(text(checkpoint["runId"]), text(checkpoint["sourceRunId"]))), "sourceCommandId": nullableText(first(text(checkpoint["commandId"]), text(checkpoint["sourceCommandId"]))), "staleMs": nil, "blocker": firstAny(objectMap(checkpoint["diagnostic"])["blocker"], checkpoint["blocker"]), "timing": timing, "startedAt": timing["startedAt"], "lastEventAt": timing["lastEventAt"], "finishedAt": timing["finishedAt"], "durationMs": timing["durationMs"], "updatedAt": updatedAt(checkpoint), "valuesRedacted": true} } func timingProjection(record map[string]any, status string) map[string]any { source := objectMap(record["timing"]) observedAt := time.Now().UTC().Format(time.RFC3339Nano) startedAt := timestamp(source["startedAt"]) lastEventAt := timestamp(source["lastEventAt"]) terminal := boolValue(record["terminal"]) || isTerminal(status) finishedAt := "" if terminal { finishedAt = timestamp(source["finishedAt"]) } return map[string]any{"startedAt": nullableText(startedAt), "lastEventAt": nullableText(lastEventAt), "finishedAt": nullableText(finishedAt), "durationMs": elapsedMs(startedAt, first(finishedAt, observedAt)), "observedAt": nilIf(terminal, observedAt), "lastEventAgeMs": nilIf(terminal, elapsedMs(lastEventAt, observedAt)), "valuesRedacted": true} } func checkpointForTrace(facts map[string][]any, traceID string) map[string]any { if traceID == "" { return map[string]any{} } for _, row := range facts["checkpoints"] { item := objectMap(row) if text(item["traceId"]) == traceID { return item } } return map[string]any{} } func turnForTrace(facts map[string][]any, traceID string) map[string]any { if traceID == "" { return map[string]any{} } for _, row := range facts["turns"] { item := objectMap(row) if text(item["traceId"]) == traceID { return item } } return map[string]any{} } func latestTraceIDForSession(facts map[string][]any, sid string) string { for _, row := range facts["turns"] { item := objectMap(row) if text(item["sessionId"]) == sid && text(item["traceId"]) != "" { return text(item["traceId"]) } } return "" } func mergeFacts(sets ...map[string][]any) map[string][]any { merged := emptyFacts() keys := map[string]string{"sessions": "sessionId", "messages": "messageId", "parts": "partId", "turns": "turnId", "traceEvents": "id", "checkpoints": "traceId"} for _, set := range sets { for family, idKey := range keys { seen := map[string]bool{} for _, row := range merged[family] { if id := text(objectMap(row)[idKey]); id != "" { seen[id] = true } } for _, row := range set[family] { id := text(objectMap(row)[idKey]) if id != "" && seen[id] { continue } merged[family] = append(merged[family], row) if id != "" { seen[id] = true } } } } return merged } func sessionListContainsRoute(sessions []map[string]any, routeID string) bool { for _, session := range sessions { if sessionID(session) == routeID || text(session["conversationId"]) == routeID { return true } } return false } func sessionID(session map[string]any) string { return first(text(session["sessionId"]), text(session["id"])) } func lastTraceID(session map[string]any) string { return first(text(session["lastTraceId"]), text(session["traceId"])) } func updatedAt(record map[string]any) any { return nullableText(first(text(record["updatedAt"]), text(record["occurredAt"]), text(record["createdAt"]))) } func mapSessions(sessions []map[string]any, fn func(map[string]any) string) []string { out := []string{} for _, session := range sessions { if value := fn(session); value != "" { out = append(out, value) } } return out } func uniqueStrings(values []string) []string { seen := map[string]bool{} out := []string{} for _, value := range values { if value != "" && !seen[value] { seen[value] = true out = append(out, value) } } return out } func cursorOffset(value string) int { text := strings.TrimSpace(value) if strings.HasPrefix(text, "idx:") { text = strings.TrimPrefix(text, "idx:") } parsed, err := strconv.Atoi(text) if err != nil || parsed < 0 { return 0 } return parsed } func cursorOrNil(offset int) any { if offset > 0 { return fmt.Sprintf("idx:%d", offset) } return nil } func nextCursor(offset int, limit int, hasMore bool) any { if hasMore { return fmt.Sprintf("idx:%d", offset+limit) } return nil } func objectMap(value any) map[string]any { if item, ok := value.(map[string]any); ok { return item } return map[string]any{} } func text(value any) string { if value == nil { return "" } if stringValue, ok := value.(string); ok { return strings.TrimSpace(stringValue) } result := strings.TrimSpace(fmt.Sprint(value)) if result == "" { return "" } return result } func nullableText(value any) any { text := text(value) if text == "" { return nil } return text } func boolValue(value any) bool { b, _ := value.(bool); return b } func nilIf(condition bool, value any) any { if condition { return nil } return value } func firstAny(values ...any) any { for _, value := range values { if value != nil { return value } } return nil } func seq(record map[string]any) any { for _, key := range []string{"projectedSeq", "sourceSeq", "seq"} { switch value := record[key].(type) { case int64: if value >= 0 { return value } case int: if value >= 0 { return value } case float64: if value >= 0 { return int64(value) } } } return nil } func timestamp(value any) string { parsed, err := time.Parse(time.RFC3339Nano, text(value)) if err == nil { return parsed.UTC().Format(time.RFC3339Nano) } parsed, err = time.Parse(time.RFC3339, text(value)) if err == nil { return parsed.UTC().Format(time.RFC3339Nano) } return "" } func elapsedMs(startedAt string, endedAt string) any { start, err := time.Parse(time.RFC3339Nano, startedAt) if err != nil { return nil } end, err := time.Parse(time.RFC3339Nano, endedAt) if err != nil || end.Before(start) { return nil } return end.Sub(start).Milliseconds() } func normalizeStatus(value any) string { status := strings.ReplaceAll(strings.ToLower(text(value)), "_", "-") if status == "cancelled" { return "canceled" } if status == "" || status == "" { return "unknown" } return status } func terminalStatus(value string) string { status := normalizeStatus(value) if isTerminal(status) { return status } return "" } func isRunning(status string) bool { switch normalizeStatus(status) { case "running", "retrying", "pending", "queued", "accepted", "dispatching", "streaming", "processing", "busy": return true default: return false } } func isTerminal(status string) bool { switch normalizeStatus(status) { case "completed", "failed", "blocked", "timeout", "canceled", "stale", "thread-resume-failed", "interrupted", "expired": return true default: return false } } func normalizeProjectionStatus(value string) string { status := normalizeStatus(value) if status == "caughtup" { return "caught-up" } switch status { case "caught-up", "projecting", "blocked", "stalled", "unknown": return status default: return "unknown" } } func normalizeProjectionHealth(value string, projectionStatus string) string { health := normalizeStatus(value) if health == "healthy" && projectionStatus == "caught-up" { return "caught-up" } if health == "healthy" && projectionStatus == "projecting" { return "projecting" } switch health { case "caught-up", "projecting", "degraded", "stalled", "unavailable", "unknown": return health default: if projectionStatus == "caught-up" || projectionStatus == "projecting" { return projectionStatus } return "unknown" } } func configFromEnv() Config { port := first(os.Getenv("HWLAB_WORKBENCH_RUNTIME_PORT"), os.Getenv("PORT"), "6671") return Config{ Addr: ":" + port, DatabaseURL: first(os.Getenv("HWLAB_WORKBENCH_RUNTIME_DB_URL"), os.Getenv("HWLAB_CLOUD_DB_URL")), QueryTimeout: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_QUERY_TIMEOUT_MS", 10000)) * time.Millisecond, ReadinessDelay: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_READY_TIMEOUT_MS", 2000)) * time.Millisecond, PoolMax: envInt("HWLAB_WORKBENCH_RUNTIME_DB_POOL_MAX", 8), Cache: cacheConfigFromEnv(), } } func (s *Server) persistenceSummary() map[string]any { return map[string]any{"ready": true, "durable": true, "adapter": "postgres", "serviceId": serviceID, "cache": s.cache.Summary(), "valuesRedacted": true} } func decodeJSON(w http.ResponseWriter, r *http.Request, target any) bool { defer r.Body.Close() decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) if err := decoder.Decode(target); err != nil { writeAPIError(w, http.StatusBadRequest, "invalid_json", "request body must be valid JSON") return false } return true } func writeQueryError(w http.ResponseWriter, err error) { log.Printf("%s query failed: %v", serviceID, err) errorPayload := map[string]any{ "code": "workbench_runtime_query_failed", "message": "workbench runtime query failed", "retryable": true, "transient": true, "causeCode": sqlErrorCode(err), "valuesRedacted": true, } if stage := queryErrorStage(err); stage != "" { errorPayload["stage"] = stage errorPayload["causeStage"] = stage } writeJSON(w, http.StatusServiceUnavailable, map[string]any{ "ok": false, "error": errorPayload, "valuesRedacted": true, }) } func writeAPIError(w http.ResponseWriter, status int, code string, message string) { writeJSON(w, status, map[string]any{"ok": false, "error": map[string]any{"code": code, "message": message, "valuesRedacted": true}, "valuesRedacted": true}) } func methodNotAllowed(w http.ResponseWriter, allow string) { w.Header().Set("Allow", allow) writeAPIError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed") } func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) } func envInt(name string, fallback int) int { value := strings.TrimSpace(os.Getenv(name)) if value == "" { return fallback } parsed, err := strconv.Atoi(value) if err != nil || parsed <= 0 { return fallback } return parsed } func first(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { return value } } return "" } func nullableString(value sql.NullString) any { if value.Valid { return value.String } return nil } func nullableStringValue(value sql.NullString) string { if value.Valid { return value.String } return "" } func nullableInt(value sql.NullInt64) any { if value.Valid { return value.Int64 } return int64(0) } func nullableBool(value sql.NullBool) bool { return value.Valid && value.Bool } func nullableTime(value sql.NullTime) string { if value.Valid { return value.Time.UTC().Format(time.RFC3339Nano) } return "" } func nullableTimeText(value sql.NullString) string { if !value.Valid { return "" } raw := strings.TrimSpace(value.String) if raw == "" { return "" } parsed, err := time.Parse(time.RFC3339Nano, raw) if err == nil { return parsed.UTC().Format(time.RFC3339Nano) } return raw } type sqlStateError interface{ SQLState() string } func sqlErrorCode(err error) string { if err == nil { return "" } var stateErr sqlStateError if errors.As(err, &stateErr) { return stateErr.SQLState() } return "" }