覆盖 kb/storage/projectworkflow/docgov/freedoc 与两个 document CLI。 Co-authored-by: Cursor <cursoragent@cursor.com>
132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/docrevision"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// document-revision-backfill dry-runs or applies controlled document output.md into the ledger.
|
|
func main() {
|
|
dsn := flag.String("dsn", os.Getenv("TEST_DATABASE_URL"), "postgres DSN")
|
|
repoRoot := flag.String("repo-root", ".", "repository root for relative work_dir")
|
|
apply := flag.Bool("apply", false, "apply changes (default dry-run)")
|
|
outPath := flag.String("report", "", "write JSON report path")
|
|
flag.Parse()
|
|
if *dsn == "" {
|
|
fmt.Fprintln(os.Stderr, "dsn or TEST_DATABASE_URL required")
|
|
os.Exit(2)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
pool, err := pgxpool.New(ctx, *dsn)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
defer pool.Close()
|
|
|
|
rows, err := pool.Query(ctx, `
|
|
SELECT doc_id, COALESCE(work_dir,''), COALESCE(content_sha256,''), COALESCE(current_state,'drafting'), project_id
|
|
FROM document_records
|
|
ORDER BY doc_id`)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
defer rows.Close()
|
|
|
|
type item struct {
|
|
DocID string `json:"doc_id"`
|
|
Status string `json:"status"`
|
|
Detail string `json:"detail,omitempty"`
|
|
SHA string `json:"sha,omitempty"`
|
|
FileSHA string `json:"file_sha,omitempty"`
|
|
}
|
|
report := struct {
|
|
DryRun bool `json:"dry_run"`
|
|
Items []item `json:"items"`
|
|
Success int `json:"success"`
|
|
Missing int `json:"missing"`
|
|
Mismatch int `json:"hash_mismatch"`
|
|
}{DryRun: !*apply}
|
|
|
|
svc := docrevision.NewService(pool)
|
|
for rows.Next() {
|
|
var docID, workDir, dbSHA, state string
|
|
var projectID int64
|
|
if err := rows.Scan(&docID, &workDir, &dbSHA, &state, &projectID); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
it := item{DocID: docID, SHA: dbSHA}
|
|
if workDir == "" {
|
|
it.Status = "missing"
|
|
it.Detail = "empty work_dir"
|
|
report.Missing++
|
|
report.Items = append(report.Items, it)
|
|
continue
|
|
}
|
|
path := workDir
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(*repoRoot, workDir)
|
|
}
|
|
body, err := os.ReadFile(filepath.Join(path, "output.md"))
|
|
if err != nil {
|
|
it.Status = "missing"
|
|
it.Detail = err.Error()
|
|
report.Missing++
|
|
report.Items = append(report.Items, it)
|
|
continue
|
|
}
|
|
canon := docrevision.CanonicalizeMarkdown(string(body))
|
|
fileSHA := docrevision.SHA256Hex(canon)
|
|
it.FileSHA = fileSHA
|
|
if dbSHA != "" && !strings.EqualFold(dbSHA, fileSHA) {
|
|
legacy := docrevision.SHA256Hex(docrevision.LegacyDestructiveCanonicalizeMarkdown(string(body)))
|
|
if strings.EqualFold(dbSHA, legacy) {
|
|
it.Status = "legacy_canonicalization_drift"
|
|
it.Detail = "LEGACY_CANONICALIZATION_DRIFT: refuse silent overwrite; use document-content-reconcile"
|
|
} else {
|
|
it.Status = "hash_mismatch"
|
|
it.Detail = "CONTENT_FORK: document_records.content_sha256 != file"
|
|
}
|
|
report.Mismatch++
|
|
report.Items = append(report.Items, it)
|
|
continue
|
|
}
|
|
if *apply {
|
|
if _, err := svc.OpenTemplateVersion(ctx, docID, canon, docrevision.ActorSnapshot{DisplayName: "backfill"}, projectID, workDir); err != nil {
|
|
it.Status = "error"
|
|
it.Detail = err.Error()
|
|
report.Items = append(report.Items, it)
|
|
continue
|
|
}
|
|
}
|
|
it.Status = "success"
|
|
report.Success++
|
|
report.Items = append(report.Items, it)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
enc, _ := json.MarshalIndent(report, "", " ")
|
|
if *outPath != "" {
|
|
_ = os.WriteFile(*outPath, enc, 0o644)
|
|
}
|
|
fmt.Println(string(enc))
|
|
if report.Mismatch > 0 {
|
|
os.Exit(3)
|
|
}
|
|
}
|