fix(watcher): never overwrite a parked note; a proposal cannot be forged or pre-empted; CRLF notes; wing dirs must be plain (#205)
From the independent review of #207: - parked notes are created O_EXCL with -2,-3 suffixes (same-named notes from different subfolders were overwritten and their originals deleted) - proposed_wing is emitted only if a plain wing name; a note's own proposal_* keys are dropped so they cannot outrank the resolver's (first-wins reads) - CRLF notes: wing: key honoured, one valid frontmatter block on parking - KnownWings skips wiki dirs that are not canonical lower-case names - adds the wingmigrate dry-run/apply command Each fix has a test that failed first and a mutant that fails it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Command wingmigrate moves pages out of the retired wiki/{sources,concepts,
|
||||
// entities}/ layout into wiki/<wing>/ (hyperguild#205 S5).
|
||||
//
|
||||
// Default is a dry run that prints the plan. -apply performs the Auto items.
|
||||
// Page text goes to -llm-url, which MUST be a local model.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/llm"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/migrate"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/wing"
|
||||
)
|
||||
|
||||
func main() {
|
||||
brain := flag.String("brain", "/var/lib/supervisor/brain", "brain directory")
|
||||
llmURL := flag.String("llm-url", "http://10.0.1.20:31234/v1", "OpenAI-compatible LOCAL endpoint")
|
||||
model := flag.String("model", "qwen35-9b-fast", "model name")
|
||||
threshold := flag.Float64("threshold", 0.9, "classifier confidence threshold")
|
||||
apply := flag.Bool("apply", false, "perform the Auto moves (default: dry run)")
|
||||
what := flag.String("what-if-eligible", "", "dry run only: comma-separated wings to treat as eligible, to see what tagging them would allow")
|
||||
flag.Parse()
|
||||
|
||||
if *apply && *what != "" {
|
||||
slog.Error("-what-if-eligible is a dry-run aid and cannot be combined with -apply: eligibility is a sovereignty decision made in classification.yaml")
|
||||
os.Exit(2)
|
||||
}
|
||||
if err := run(*brain, *llmURL, *model, *threshold, *apply, *what); err != nil {
|
||||
slog.Error("wingmigrate", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(brain, llmURL, model string, threshold float64, apply bool, whatIf string) error {
|
||||
client := llm.New(llmURL, "", model, 3*time.Minute)
|
||||
base, err := wing.NewConfiguredResolver(brain, client.Complete, threshold)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whatIf != "" {
|
||||
allowed := map[string]bool{}
|
||||
for _, w := range strings.Split(whatIf, ",") {
|
||||
allowed[strings.TrimSpace(w)] = true
|
||||
}
|
||||
base.Eligible = func(w string) bool { return allowed[w] }
|
||||
}
|
||||
rf := func(exclude string) *wing.Resolver {
|
||||
r := *base
|
||||
r.Scorer = wing.NewBM25Scorer(brain, base.Known, exclude)
|
||||
return &r
|
||||
}
|
||||
items, err := migrate.BuildPlan(context.Background(), brain, rf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report(items)
|
||||
if !apply {
|
||||
fmt.Println("\n_dry run: nothing was changed_")
|
||||
return nil
|
||||
}
|
||||
n, err := migrate.Apply(brain, items)
|
||||
fmt.Printf("\napplied %d moves\n", n)
|
||||
return err
|
||||
}
|
||||
|
||||
func report(items []migrate.Item) {
|
||||
auto, exc := 0, 0
|
||||
for _, it := range items {
|
||||
if it.Status == migrate.Auto {
|
||||
auto++
|
||||
} else {
|
||||
exc++
|
||||
}
|
||||
}
|
||||
fmt.Printf("# wing migration plan\n\n%d pages: %d auto, %d exceptions\n\n", len(items), auto, exc)
|
||||
fmt.Println("| status | kind | from | to / proposal | how | why |")
|
||||
fmt.Println("|---|---|---|---|---|---|")
|
||||
for _, it := range items {
|
||||
dest := it.To
|
||||
if dest == "" {
|
||||
dest = it.Wing
|
||||
}
|
||||
fmt.Printf("| %s | %s | %s | %s | %s | %s |\n", it.Status, it.Kind, it.From, dest, it.Method, strings.ReplaceAll(it.Reason, "|", "/"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user