fix(server): cache health probes, surface SSE marshal errors, fail closed on missing duties

Health checks reuse a two-second snapshot and bound the schema query. Task SSE writes an error frame when an event cannot be encoded. ResolveDocTask errors when the frozen plan has no duty row, while explicit none-duties stay zero. Drop the unwired semantic-summary replace env, record the intentional dev versus release model split, document the test/main cutover path, and archive pre-goose SQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-19 09:16:18 +00:00
co-authored by Cursor
parent 4c34e8a503
commit 265f09c1f2
24 changed files with 66 additions and 11 deletions
@@ -0,0 +1,4 @@
# Pre-goose SQL archive
These files predate Goose and are not applied by env-service or any script.
Live schema is deploy/postgres/goose/. Kept for history only.
+2 -2
View File
@@ -41,6 +41,7 @@ services:
DATA_DIR: /app/data/workspaces
MINIMAX_ENDPOINT_URL: ${MINIMAX_ENDPOINT_URL:-https://api.minimaxi.com/v1}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
# dev iterates on highspeed; test/main pin MiniMax-M3. Do not align without a promotion decision.
MINIMAX_MODEL_ID: ${MINIMAX_MODEL_ID:-MiniMax-M2.7-highspeed}
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax
@@ -114,9 +115,8 @@ services:
MULTI_INTENT_U10_MUTATE: ${MULTI_INTENT_U10_MUTATE:-false}
MULTI_INTENT_U11_DOCGOV: ${MULTI_INTENT_U11_DOCGOV:-false}
MULTI_INTENT_U12_RUN_CONTROL: ${MULTI_INTENT_U12_RUN_CONTROL:-false}
# W2 PU6: shadow semantic summary; REPLACE stays unwired even if env is true
# W2 PU6: shadow semantic summary only. REPLACE stays unwired and is not declared here.
SEMANTIC_SUMMARY_SHADOW: ${SEMANTIC_SUMMARY_SHADOW:-false}
SEMANTIC_SUMMARY_REPLACE: ${SEMANTIC_SUMMARY_REPLACE:-false}
PI_CHANGE_REPORT_NARRATIVE: ${PI_CHANGE_REPORT_NARRATIVE:-false}
# FAV U7: keep degraded read + in-process publisher off until separate approval
FAV_DEGRADED_READ: ${FAV_DEGRADED_READ:-false}
+1
View File
@@ -41,6 +41,7 @@ services:
DATA_DIR: /app/data/workspaces
MINIMAX_ENDPOINT_URL: ${MINIMAX_ENDPOINT_URL:-https://api.minimaxi.com/v1}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
# Release pin. dev uses MiniMax-M2.7-highspeed on purpose.
MINIMAX_MODEL_ID: ${MINIMAX_MODEL_ID:-MiniMax-M3}
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax
+1
View File
@@ -41,6 +41,7 @@ services:
DATA_DIR: /app/data/workspaces
MINIMAX_ENDPOINT_URL: ${MINIMAX_ENDPOINT_URL:-https://api.minimaxi.com/v1}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
# Release pin. dev uses MiniMax-M2.7-highspeed on purpose.
MINIMAX_MODEL_ID: ${MINIMAX_MODEL_ID:-MiniMax-M3}
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax
+6
View File
@@ -12,6 +12,12 @@ import (
const OpsCutoverEnvironment = "dev"
// OpsCutoverPromotionPath is how test and main change consumer modes.
// ApplyOpsCutover stays dev-only. After a dev observation window, copy the
// reviewed mode with an authorized promotion receipt (SQL or migration), first
// on test, then on main. Do not widen ValidateOpsCutover to accept those envs.
const OpsCutoverPromotionPath = "dev-observe -> promotion-receipt on test -> same receipt on main; ApplyOpsCutover rejects non-dev"
// ValidateOpsCutover is the shared-dev gate for cluster mode changes.
// test/main, FAV, and unknown consumers are rejected before any write.
func ValidateOpsCutover(environment, consumer, mode string) error {
+7 -1
View File
@@ -1,6 +1,9 @@
package events
import "testing"
import (
"strings"
"testing"
)
func TestValidateOpsCutoverRejectsFavAndNonDev(t *testing.T) {
if err := ValidateOpsCutover("dev", ConsumerNotify, ModeShadow); err != nil {
@@ -12,6 +15,9 @@ func TestValidateOpsCutoverRejectsFavAndNonDev(t *testing.T) {
if err := ValidateOpsCutover("main", ConsumerNotify, ModeLive); err == nil {
t.Fatal("main env must fail")
}
if !strings.Contains(OpsCutoverPromotionPath, "test") || !strings.Contains(OpsCutoverPromotionPath, "main") {
t.Fatalf("promotion path must name test and main: %s", OpsCutoverPromotionPath)
}
if err := ValidateOpsCutover("dev", ConsumerFAV, ModeOff); err == nil {
t.Fatal("fav must fail even at off")
}
+31 -7
View File
@@ -86,27 +86,47 @@ type TaskRunRequestDTO struct {
}
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
resp, code := s.buildHealthResponse(false)
resp, code := s.buildHealthResponse(r.Context(), false)
writeJSONResponse(w, code, resp)
}
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
resp, code := s.buildHealthResponse(true)
resp, code := s.buildHealthResponse(r.Context(), true)
writeJSONResponse(w, code, resp)
}
func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
type healthSnapshot struct {
at time.Time
strict bool
resp HealthResponse
code int
ok bool
}
func (s *Server) buildHealthResponse(ctx context.Context, strict bool) (HealthResponse, int) {
const healthCacheTTL = 2 * time.Second
s.healthMu.Lock()
if s.healthCache.ok && s.healthCache.strict == strict && time.Since(s.healthCache.at) < healthCacheTTL {
resp, code := s.healthCache.resp, s.healthCache.code
s.healthMu.Unlock()
return resp, code
}
s.healthMu.Unlock()
dbCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
dbReady := s.svc != nil && s.svc.DatabaseReady()
schemaVersion := int64(0)
if s.svc != nil {
if dbReady {
schemaVersion = s.svc.LiveSchemaVersion(context.Background())
schemaVersion = s.svc.LiveSchemaVersion(dbCtx)
} else {
schemaVersion = s.svc.SchemaVersion()
}
}
schemaReady := schemaVersion > 0 || (s.cfg != nil && s.cfg.AllowInMemory)
taskStoreReady := taskrun.TablesReady(context.Background(), s.pool)
taskStoreReady := taskrun.TablesReady(dbCtx, s.pool)
status := "ok"
code := http.StatusOK
@@ -122,7 +142,7 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
kbReady := s.kb != nil && s.kb.Online()
return HealthResponse{
resp := HealthResponse{
Status: status,
Env: s.cfg.Env,
AgentBackend: s.cfg.AgentBackend,
@@ -133,7 +153,11 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
SchemaVersion: schemaVersion,
SchemaReady: schemaReady,
TaskStoreReady: taskStoreReady,
}, code
}
s.healthMu.Lock()
s.healthCache = healthSnapshot{at: time.Now(), strict: strict, resp: resp, code: code, ok: true}
s.healthMu.Unlock()
return resp, code
}
func (s *Server) handleGetProject(w http.ResponseWriter, r *http.Request) {
+2
View File
@@ -80,6 +80,8 @@ type Server struct {
vaultStop chan struct{}
reminderStop chan struct{}
loginLimiter *loginRateLimiter
healthMu sync.Mutex
healthCache healthSnapshot
}
// NewFromRuntime attaches the HTTP adapter to an already-wired composition root.
+7 -1
View File
@@ -433,7 +433,13 @@ func (s *Server) handleTaskRunEvents(w http.ResponseWriter, r *http.Request) {
writeStoredEvents := func(events []taskrun.StoredEvent) error {
for _, stored := range events {
data, _ := json.Marshal(stored.Event)
data, err := json.Marshal(stored.Event)
if err != nil {
if _, werr := fmt.Fprintf(w, "event: error\ndata: {\"error\":\"event_marshal_failed\",\"seq\":%d}\n\n", stored.Seq); werr != nil {
return werr
}
return fmt.Errorf("marshal stored event seq %d: %w", stored.Seq, err)
}
if _, err := fmt.Fprintf(w, "id: %d\n", stored.Seq); err != nil {
return err
}
@@ -66,6 +66,7 @@ func (o *Orchestrator) ResolveDocTask(ctx context.Context, projectID, docTaskIns
res.TemplateVersion = def.TemplateVersion
}
}
matchedDuty := false
for _, stPlan := range plan.Stages {
if stPlan.StageKey != st.StageKey {
continue
@@ -77,8 +78,12 @@ func (o *Orchestrator) ResolveDocTask(ctx context.Context, projectID, docTaskIns
res.AuthorUserID = dtPlan.Duties.AuthorUserID
res.ReviewerUserID = dtPlan.Duties.ReviewerUserID
res.ApproverUserID = dtPlan.Duties.ApproverUserID
matchedDuty = true
}
}
if !matchedDuty {
return nil, fmt.Errorf("orchestrator: frozen plan has no duties for stage %s task %s", st.StageKey, dt.TaskKey)
}
return res, nil
}
}