Add ValidateOpsCutover, eventmode CLI, and a host script that pin agent_system_dev and refuse FAV plus test/main before any cluster mode write. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/config"
|
|
"chat-agent-system/internal/events"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
envPath := os.Getenv("EVENTMODE_ENV")
|
|
if envPath == "" {
|
|
envPath = "configs/.env.dev"
|
|
}
|
|
cfg, err := config.Load(envPath)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "eventmode: load env")
|
|
os.Exit(1)
|
|
}
|
|
if cfg.Env != events.OpsCutoverEnvironment {
|
|
fmt.Fprintln(os.Stderr, "eventmode: env must be dev")
|
|
os.Exit(1)
|
|
}
|
|
if cfg.ContextDbName != "agent_system_dev" {
|
|
fmt.Fprintln(os.Stderr, "eventmode: database must be agent_system_dev")
|
|
os.Exit(1)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
pool, err := pgxpool.New(ctx, cfg.ContextDSN())
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "eventmode: pool")
|
|
os.Exit(1)
|
|
}
|
|
defer pool.Close()
|
|
|
|
switch os.Args[1] {
|
|
case "inventory":
|
|
if err := runInventory(ctx, pool); err != nil {
|
|
fmt.Fprintln(os.Stderr, "eventmode: inventory failed")
|
|
os.Exit(1)
|
|
}
|
|
case "set":
|
|
if len(os.Args) != 4 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
consumer, mode := os.Args[2], os.Args[3]
|
|
before, _, _ := events.LoadMode(ctx, pool, cfg.Env, consumer)
|
|
if err := events.ApplyOpsCutover(ctx, pool, cfg.Env, consumer, mode); err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
after, rev, _ := events.LoadMode(ctx, pool, cfg.Env, consumer)
|
|
enc := json.NewEncoder(os.Stdout)
|
|
_ = enc.Encode(map[string]any{
|
|
"environment": cfg.Env, "consumer": consumer,
|
|
"before": before, "after": after, "revision": rev,
|
|
})
|
|
case "quarantine-illegal":
|
|
n, err := events.QuarantineIllegalPending(ctx, pool, "cursor-ops", "phase2-shared-live")
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "eventmode: quarantine failed")
|
|
os.Exit(1)
|
|
}
|
|
_ = json.NewEncoder(os.Stdout).Encode(map[string]any{"quarantined": n})
|
|
default:
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func runInventory(ctx context.Context, pool *pgxpool.Pool) error {
|
|
modes, err := events.ListModes(ctx, pool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
backlog, err := events.ListBacklog(ctx, pool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pending, err := events.ClassifyPending(ctx, pool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
type wire struct {
|
|
EventID string `json:"event_id"`
|
|
Consumer string `json:"consumer"`
|
|
Classification string `json:"classification"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
class := make([]wire, 0, len(pending))
|
|
for _, r := range pending {
|
|
class = append(class, wire{r.EventID, r.Consumer, r.Classification, r.Reason})
|
|
}
|
|
return json.NewEncoder(os.Stdout).Encode(map[string]any{
|
|
"modes": modes, "backlog": backlog, "pending_classifications": class,
|
|
})
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage: eventmode inventory | set <consumer> <mode> | quarantine-illegal")
|
|
}
|