feat(events): gate shared-dev consumer mode changes
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>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user