wipe: one command empties the box and leaves it standing (V-494)
There was no documented way to repair a poisoned box. Two invented facts written during QA disabled world answering for every later turn (V-470), and revert voids the SQL row while leaving the vector behind (V-493). This is the operation that undoes both. Store.Wipe drops every table sqlite_master reports and rebuilds from schema.sql plus the migrations, rather than deleting from a hand-written list. A list has to be edited whenever a table is added, and the once it is not, the wipe leaves personal data behind while reporting success. It vacuums afterwards, because free pages still hold readable text. mavend -wipe prints every table and its row count and exits. That alone is a dry run and answers what a QA session actually asks: what is on this box. It deletes only with -confirm-wipe. Two flags, because the destructive reading of one flag is the reading a mistyped command gets. Nothing outside the database moves. Config, models, passkeys.json and the encryption key are files. QA isolation and onboarding are the other two thirds of V-494 and are not here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SoL7EBdYC5Mhz3DJd49GJy
This commit is contained in:
@@ -128,6 +128,8 @@ func run(args []string) error {
|
||||
wrappedKeyPath := flag.String("wrapped-key-file", "", "path to wrapped encryption key blob (enables cold-start unlock)")
|
||||
reembed := flag.Bool("reembed", false, "re-embed every stored note and fact with the configured embedder, then serve normally (run once after an embedder swap; the daemon does not answer until it finishes)")
|
||||
allowSeed := flag.Bool("allow-seed", false, "enable the backdated seed_event write path (QA only: it lets a caller place a fact in the past and mint a routine the tick loop will then act on; off means the method has nothing to write with)")
|
||||
wipe := flag.Bool("wipe", false, "print every table and its row count, then exit without serving; add -confirm-wipe to delete all of it")
|
||||
confirmWipe := flag.Bool("confirm-wipe", false, "with -wipe, actually remove every piece of personal data (facts, notes, vectors, events, tasks, sessions, traces, voiceprints). config, models, passkeys and the encryption key are files and survive")
|
||||
flag.CommandLine.Parse(args)
|
||||
reembedOnStart = *reembed
|
||||
allowSeedOnStart = *allowSeed
|
||||
@@ -210,6 +212,14 @@ func run(args []string) error {
|
||||
}()
|
||||
}
|
||||
|
||||
// ----- wipe: never serves, exits when it is done (Vikunja #494) -----
|
||||
if *wipe {
|
||||
if locked {
|
||||
return fmt.Errorf("wipe: the store is locked and there is no key to open it with")
|
||||
}
|
||||
return runWipe(ctx, st, os.Stdout, *confirmWipe)
|
||||
}
|
||||
|
||||
// ----- daemon components (only wired when unlocked) -----
|
||||
// Pre-declare so the unlock path can wire them later.
|
||||
var (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kami/maven/internal/store"
|
||||
)
|
||||
|
||||
// runWipe implements the -wipe flag: it prints what the database holds, and
|
||||
// removes it only when the operator also passed -confirm-wipe (Vikunja #494).
|
||||
//
|
||||
// Two flags rather than one, because the destructive reading of a single flag
|
||||
// is the reading a mistyped command gets. Without the confirmation this is a
|
||||
// dry run that costs nothing and answers the question a QA session actually
|
||||
// has — what is on this box right now.
|
||||
//
|
||||
// It runs before any daemon component is wired, so nothing is writing while
|
||||
// the tables go. The daemon exits afterwards rather than serving a store it
|
||||
// just emptied, because every component that read the old rows at boot would
|
||||
// still be holding them.
|
||||
func runWipe(ctx context.Context, st *store.Store, out io.Writer, confirmed bool) error {
|
||||
counts, err := st.WipeCounts(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wipe: read counts: %w", err)
|
||||
}
|
||||
total := 0
|
||||
for _, c := range counts {
|
||||
total += c.Rows
|
||||
fmt.Fprintf(out, " %-24s %d\n", c.Table, c.Rows)
|
||||
}
|
||||
fmt.Fprintf(out, " %-24s %d rows in %d tables\n", "TOTAL", total, len(counts))
|
||||
|
||||
if !confirmed {
|
||||
fmt.Fprintln(out, "\nnothing was deleted. pass -confirm-wipe to delete all of it.")
|
||||
fmt.Fprintln(out, "config, models, passkeys and the encryption key are files and are never touched.")
|
||||
return nil
|
||||
}
|
||||
if err := st.Wipe(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out, "\nwiped. %d rows gone, the schema is intact, mavend knows nobody.\n", total)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user