fix(cli): require positive live-dev identity and exclusive backups

Live apply now accepts only :5433/agent_system_dev plus a data/dev
host root, maps stored work dirs fail-closed, and reports unique
O_EXCL backups after a parent-directory fsync.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-14 02:30:11 +00:00
co-authored by Cursor
parent 96fd0e9e6f
commit e3b72ad64b
11 changed files with 400 additions and 139 deletions
+90 -28
View File
@@ -2,6 +2,7 @@ package main
import (
"fmt"
"net/url"
"path/filepath"
"strings"
)
@@ -9,6 +10,8 @@ import (
const (
envDev = "dev"
confirmRepairSingleDev = "REPAIR_SINGLE_DEV_DOC"
devDatabaseName = "agent_system_dev"
devPort = "5433"
)
type cliConfig struct {
@@ -31,15 +34,12 @@ func validateCLI(cfg cliConfig) error {
if cfg.Apply && strings.TrimSpace(cfg.BackupDir) == "" {
return fmt.Errorf("--backup-dir is required with --apply")
}
if (cfg.StoredDataRoot == "") != (cfg.HostDataRoot == "") {
if (strings.TrimSpace(cfg.StoredDataRoot) == "") != (strings.TrimSpace(cfg.HostDataRoot) == "") {
return fmt.Errorf("--stored-data-root and --host-data-root must be set together")
}
dsnEnv := sharedDSNEnv(cfg.DSN)
pathEnv := firstSharedPathEnv(cfg.RepoRoot, cfg.BackupDir, cfg.StoredDataRoot, cfg.HostDataRoot)
if !cfg.Live {
if dsnEnv != "" || pathEnv != "" {
if sharedDSNEnv(cfg.DSN) != "" || firstSharedPathEnv(cfg.RepoRoot, cfg.BackupDir, cfg.StoredDataRoot, cfg.HostDataRoot) != "" {
return fmt.Errorf("refusing shared/env data path or DSN; use isolated TEST_DATABASE_URL, or pass --live --environment dev --doc-id and confirm")
}
return nil
@@ -51,11 +51,17 @@ func validateCLI(cfg cliConfig) error {
if strings.TrimSpace(cfg.DocID) == "" {
return fmt.Errorf("live mode forbids batch operations; --doc-id is required")
}
if dsnEnv != "" && dsnEnv != envDev {
return fmt.Errorf("live mode refuses shared %s DSN", dsnEnv)
if err := requireLiveDevDSN(cfg.DSN); err != nil {
return err
}
if pathEnv != "" && pathEnv != envDev {
return fmt.Errorf("live mode refuses shared %s data path", pathEnv)
if strings.TrimSpace(cfg.StoredDataRoot) == "" || strings.TrimSpace(cfg.HostDataRoot) == "" {
return fmt.Errorf("live mode requires --stored-data-root and --host-data-root")
}
if !isDevDataRoot(cfg.HostDataRoot) {
return fmt.Errorf("live --environment dev requires --host-data-root to be a data/dev directory")
}
if env := sharedPathEnv(cfg.HostDataRoot); env != envDev {
return fmt.Errorf("live mode refuses host-data-root that is not positively data/dev")
}
if cfg.Apply && strings.TrimSpace(cfg.Confirm) != confirmRepairSingleDev {
return fmt.Errorf("live apply requires --confirm %s", confirmRepairSingleDev)
@@ -63,13 +69,75 @@ func validateCLI(cfg cliConfig) error {
return nil
}
func requireLiveDevDSN(dsn string) error {
port, db, err := inspectPostgresDSN(dsn)
if err != nil {
return fmt.Errorf("live mode: %w", err)
}
if port != devPort || db != devDatabaseName {
return fmt.Errorf("live --environment dev requires DSN port :%s and database %s (got port=%q db=%q)", devPort, devDatabaseName, port, db)
}
return nil
}
func inspectPostgresDSN(dsn string) (port, db string, err error) {
dsn = strings.TrimSpace(dsn)
if dsn == "" {
return "", "", fmt.Errorf("empty dsn")
}
if strings.Contains(dsn, "://") {
u, perr := url.Parse(dsn)
if perr != nil {
return "", "", fmt.Errorf("invalid dsn: %w", perr)
}
return u.Port(), strings.Trim(u.Path, "/"), nil
}
for _, part := range strings.Fields(dsn) {
k, v, ok := strings.Cut(part, "=")
if !ok {
continue
}
switch k {
case "port":
port = v
case "dbname":
db = v
}
}
if port == "" && strings.Contains(dsn, ":"+devPort) {
port = devPort
}
if db == "" {
return "", "", fmt.Errorf("dsn is not a recognizable postgres URL or keyword string")
}
return port, db, nil
}
func isDevDataRoot(p string) bool {
clean := filepath.ToSlash(filepath.Clean(strings.TrimSpace(p)))
return clean == "data/dev" || strings.HasSuffix(clean, "/data/dev")
}
func sharedDSNEnv(dsn string) string {
switch {
case strings.Contains(dsn, ":5433"):
port, _, err := inspectPostgresDSN(dsn)
if err != nil {
switch {
case strings.Contains(dsn, ":5433"):
return envDev
case strings.Contains(dsn, ":5434"):
return "test"
case strings.Contains(dsn, ":5435"):
return "main"
default:
return ""
}
}
switch port {
case "5433":
return envDev
case strings.Contains(dsn, ":5434"):
case "5434":
return "test"
case strings.Contains(dsn, ":5435"):
case "5435":
return "main"
default:
return ""
@@ -90,20 +158,14 @@ func sharedPathEnv(p string) string {
return ""
}
clean := filepath.ToSlash(filepath.Clean(p))
needles := []struct {
env string
tokens []string
}{
{envDev, []string{"/data/dev", "data/dev"}},
{"test", []string{"/data/test", "data/test"}},
{"main", []string{"/data/main", "data/main"}},
switch {
case clean == "data/dev" || strings.HasSuffix(clean, "/data/dev") || strings.Contains(clean, "/data/dev/"):
return envDev
case clean == "data/test" || strings.HasSuffix(clean, "/data/test") || strings.Contains(clean, "/data/test/"):
return "test"
case clean == "data/main" || strings.HasSuffix(clean, "/data/main") || strings.Contains(clean, "/data/main/"):
return "main"
default:
return ""
}
for _, n := range needles {
for _, token := range n.tokens {
if strings.Contains(clean, token) {
return n.env
}
}
}
return ""
}
+64 -16
View File
@@ -19,21 +19,29 @@ func TestValidateCLI_DefaultRefusesSharedDev(t *testing.T) {
}
}
func TestValidateCLI_LiveApplyRequiresConfirmAndDocID(t *testing.T) {
base := cliConfig{
func validLiveApply() cliConfig {
return cliConfig{
DSN: "postgres://app:x@127.0.0.1:5433/agent_system_dev?sslmode=disable",
RepoRoot: "/repo/data/dev",
DocID: "doc_proj143_intake",
Apply: true,
Live: true,
Environment: "dev",
Confirm: confirmRepairSingleDev,
BackupDir: "/tmp/backup",
StoredDataRoot: "/app/data",
HostDataRoot: "/repo/data/dev",
}
}
func TestValidateCLI_LiveApplyRequiresConfirmAndDocID(t *testing.T) {
base := validLiveApply()
base.DocID = ""
if err := validateCLI(base); err == nil {
t.Fatal("live apply without --doc-id must fail")
}
base.DocID = "doc_proj143_intake"
base.Confirm = ""
if err := validateCLI(base); err == nil {
t.Fatal("live apply without confirm must fail")
}
@@ -47,18 +55,67 @@ func TestValidateCLI_LiveApplyRequiresConfirmAndDocID(t *testing.T) {
}
}
func TestValidateCLI_LiveRequiresPositiveDevDSNAndHostRoot(t *testing.T) {
cases := []struct {
name string
mut func(*cliConfig)
}{
{"port 5433 but wrong db", func(c *cliConfig) {
c.DSN = "postgres://app:x@127.0.0.1:5433/agent_itest?sslmode=disable"
}},
{"dev db name but isolated port", func(c *cliConfig) {
c.DSN = "postgres://app:x@127.0.0.1:55432/agent_system_dev?sslmode=disable"
}},
{"empty dsn env must not pass live", func(c *cliConfig) {
c.DSN = "postgres://app:x@127.0.0.1:55432/agent_itest?sslmode=disable"
}},
{"host root not data/dev", func(c *cliConfig) {
c.HostDataRoot = "/tmp/isolated-host"
}},
{"missing mapping roots", func(c *cliConfig) {
c.StoredDataRoot = ""
c.HostDataRoot = ""
}},
{"host root is data/test", func(c *cliConfig) {
c.HostDataRoot = "/repo/data/test"
}},
{"keyword dsn missing port", func(c *cliConfig) {
c.DSN = "host=127.0.0.1 dbname=agent_system_dev"
}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg := validLiveApply()
tc.mut(&cfg)
if err := validateCLI(cfg); err == nil {
t.Fatalf("%s: live must fail closed", tc.name)
}
})
}
}
func TestValidateCLI_LiveKeywordDSNAccepted(t *testing.T) {
cfg := validLiveApply()
cfg.DSN = "host=127.0.0.1 port=5433 dbname=agent_system_dev sslmode=disable"
if err := validateCLI(cfg); err != nil {
t.Fatal(err)
}
}
func TestValidateCLI_LiveForbidsBatchAndNonDev(t *testing.T) {
err := validateCLI(cliConfig{
DSN: "postgres://app:x@127.0.0.1:5434/agent_system_test?sslmode=disable",
Live: true,
Environment: "dev",
DocID: "doc_x",
DSN: "postgres://app:x@127.0.0.1:5434/agent_system_test?sslmode=disable",
Live: true,
Environment: "dev",
DocID: "doc_x",
StoredDataRoot: "/app/data",
HostDataRoot: "/repo/data/dev",
})
if err == nil {
t.Fatal("live mode must refuse :5434")
}
err = validateCLI(cliConfig{
DSN: "postgres://app:x@127.0.0.1:55432/itest?sslmode=disable",
DSN: "postgres://app:x@127.0.0.1:5433/agent_system_dev?sslmode=disable",
Live: true,
Environment: "test",
DocID: "doc_x",
@@ -66,15 +123,6 @@ func TestValidateCLI_LiveForbidsBatchAndNonDev(t *testing.T) {
if err == nil {
t.Fatal("live mode must refuse non-dev environment")
}
err = validateCLI(cliConfig{
DSN: "postgres://app:x@127.0.0.1:5433/agent_system_dev?sslmode=disable",
RepoRoot: "/repo/data/dev",
Live: true,
Environment: "dev",
})
if err == nil {
t.Fatal("live mode must refuse batch without --doc-id")
}
}
func TestValidateCLI_IsolatedDefaultAllowsApply(t *testing.T) {