Approve/commit now write FAV staging in the domain transaction, recover-apply materializes scratch collections, and unpublished or GET paths cannot mutate the registry. Co-authored-by: Cursor <cursoragent@cursor.com>
227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/artifactvault"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Fprintln(os.Stderr, "usage: artifact-vault <backfill|inventory|verify|recover-plan|recover-apply|gc-preflight|destroy> [flags]")
|
|
os.Exit(2)
|
|
}
|
|
cmd := os.Args[1]
|
|
os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
|
|
switch cmd {
|
|
case "backfill":
|
|
runBackfill()
|
|
case "inventory":
|
|
runInventory()
|
|
case "verify":
|
|
runVerify()
|
|
case "recover-plan":
|
|
runRecoverPlan()
|
|
case "recover-apply":
|
|
runRecoverApply()
|
|
case "gc-preflight":
|
|
runGCPreflight()
|
|
case "destroy":
|
|
runDestroy()
|
|
default:
|
|
fmt.Fprintln(os.Stderr, "unknown command:", cmd)
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func runBackfill() {
|
|
dsn := flag.String("dsn", os.Getenv("APP_DB_DSN"), "postgres DSN")
|
|
env := flag.String("env", "dev", "environment")
|
|
flag.Parse()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
pool, err := mustPool(ctx, *dsn)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
rows, err := artifactvault.EnumerateTerminals(ctx, pool, *env)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
ex, err := artifactvault.LoadExclusions(ctx, pool)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
reg := artifactvault.NewPGRegistry(pool)
|
|
rep, err := artifactvault.Backfill(ctx, reg, rows, ex)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(rep)
|
|
}
|
|
|
|
func runInventory() {
|
|
root := flag.String("root", os.Getenv("VAULT_STATE_ROOT"), "vault root")
|
|
ident := flag.String("identity", os.Getenv("VAULT_STORAGE_IDENTITY"), "vault identity")
|
|
flag.Parse()
|
|
if *root == "" || *ident == "" {
|
|
fatal(fmt.Errorf("root and identity required"))
|
|
}
|
|
st, err := artifactvault.NewFilesystemStore(*root, *ident, []byte(nz(os.Getenv("VAULT_MASTER_KEY"), "0123456789abcdef0123456789abcdef")))
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(map[string]any{"identity_ok": st.StorageIdentity != "", "root_exists": true, "identity": "omitted"})
|
|
}
|
|
|
|
func runVerify() {
|
|
dsn := flag.String("dsn", os.Getenv("APP_DB_DSN"), "postgres DSN")
|
|
flag.Parse()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
pool, err := mustPool(ctx, *dsn)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
reg := artifactvault.NewPGRegistry(pool)
|
|
cov := reg.Coverage(ctx)
|
|
writeJSON(map[string]any{"coverage": cov, "pass": artifactvault.CoveragePASS(cov)})
|
|
}
|
|
|
|
func runRecoverPlan() {
|
|
target := flag.String("target-dsn", "", "scratch DSN")
|
|
ident := flag.String("target-identity", "", "scratch identity")
|
|
mode := flag.String("mode", string(artifactvault.RecoverAsCollection), "recover mode")
|
|
flag.Parse()
|
|
plan := artifactvault.RecoverPlan{
|
|
Mode: artifactvault.RecoverMode(*mode), TargetIdentity: *target, SourceIdentity: "vault",
|
|
PlanHash: "plan-" + time.Now().UTC().Format("20060102"), HasAdmin: true, ScratchOnly: true,
|
|
}
|
|
if err := artifactvault.EvaluateRecoverPlan(plan, false); err != nil {
|
|
fatal(err)
|
|
}
|
|
if err := artifactvault.ValidateRecoverTarget(*target, *ident); err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(plan)
|
|
}
|
|
|
|
func runRecoverApply() {
|
|
sourceRoot := flag.String("source-root", os.Getenv("VAULT_STATE_ROOT"), "vault root")
|
|
destDir := flag.String("dest-dir", "", "scratch collection directory")
|
|
target := flag.String("target-dsn", "", "scratch DSN")
|
|
ident := flag.String("target-identity", "", "scratch identity")
|
|
sourceIdent := flag.String("identity", os.Getenv("VAULT_STORAGE_IDENTITY"), "source vault identity")
|
|
confirm := flag.String("confirm", "", "must equal plan hash")
|
|
flag.Parse()
|
|
if err := artifactvault.ValidateRecoverTarget(*target, *ident); err != nil {
|
|
fatal(err)
|
|
}
|
|
if *confirm == "" {
|
|
fatal(fmt.Errorf("confirm plan hash required"))
|
|
}
|
|
if *sourceRoot == "" || *sourceIdent == "" {
|
|
fatal(fmt.Errorf("source-root and identity required"))
|
|
}
|
|
plan := artifactvault.RecoverPlan{
|
|
Mode: artifactvault.RecoverAsCollection, TargetIdentity: *target, SourceIdentity: *ident,
|
|
PlanHash: *confirm, HasAdmin: true, ScratchOnly: true,
|
|
}
|
|
store, err := artifactvault.NewFilesystemStore(*sourceRoot, *sourceIdent, []byte(nz(os.Getenv("VAULT_MASTER_KEY"), "0123456789abcdef0123456789abcdef")))
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
arts, payloads, err := store.LoadCommittedArtifacts()
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
denied := map[string]bool{}
|
|
for _, art := range arts {
|
|
if art.PublicationStatus == artifactvault.StatusTombstoned || store.HasTombstone(art.UUID) {
|
|
denied[art.UUID] = true
|
|
}
|
|
}
|
|
out, err := artifactvault.ApplyRecoverAsCollection(plan, *destDir, arts, payloads, denied)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(out)
|
|
}
|
|
|
|
func runDestroy() {
|
|
dsn := flag.String("dsn", os.Getenv("APP_DB_DSN"), "postgres DSN")
|
|
backupOK := flag.Bool("backup-ok", false, "complementary backup PASS")
|
|
corpusOK := flag.Bool("corpus-ok", false, "migration corpus PASS")
|
|
confirm := flag.String("confirm", "", "must equal DESTROY")
|
|
flag.Parse()
|
|
if *confirm != "DESTROY" {
|
|
fatal(fmt.Errorf("destroy requires --confirm=DESTROY"))
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancel()
|
|
pool, err := mustPool(ctx, *dsn)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
reg := artifactvault.NewPGRegistry(pool)
|
|
if err := artifactvault.PreflightDestroy(reg.Coverage(ctx), *backupOK, *corpusOK, true); err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(map[string]string{"status": "preflight-pass", "note": "dataset wipe is operator-owned after complementary PASS"})
|
|
}
|
|
|
|
func runGCPreflight() {
|
|
dsn := flag.String("dsn", os.Getenv("APP_DB_DSN"), "postgres DSN")
|
|
backupOK := flag.Bool("backup-ok", false, "complementary backup PASS")
|
|
corpusOK := flag.Bool("corpus-ok", false, "migration corpus PASS")
|
|
flag.Parse()
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancel()
|
|
pool, err := mustPool(ctx, *dsn)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
reg := artifactvault.NewPGRegistry(pool)
|
|
if err := artifactvault.PreflightDestroy(reg.Coverage(ctx), *backupOK, *corpusOK, true); err != nil {
|
|
fatal(err)
|
|
}
|
|
writeJSON(map[string]string{"status": "pass"})
|
|
}
|
|
|
|
func mustPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
|
if dsn == "" {
|
|
return nil, fmt.Errorf("dsn required")
|
|
}
|
|
return pgxpool.New(ctx, dsn)
|
|
}
|
|
|
|
func writeJSON(v any) {
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
_ = enc.Encode(v)
|
|
}
|
|
|
|
func fatal(err error) {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func nz(v, def string) string {
|
|
if strings.TrimSpace(v) == "" {
|
|
return def
|
|
}
|
|
return v
|
|
}
|