feat: protect agent plan edits and recover revisions (#3745)
This commit is contained in:
@@ -198,6 +198,12 @@ func (r *Repository) runMigrations() error {
|
||||
r.migrate.Apply("task_plans.implementation_started_session_id", `ALTER TABLE task_plans ADD COLUMN implementation_started_session_id TEXT`)
|
||||
r.migrate.Apply("task_plans.implementation_started_by", `ALTER TABLE task_plans ADD COLUMN implementation_started_by TEXT`)
|
||||
_ = r.migrate.Apply("task_plans.comments_revision", `ALTER TABLE task_plans ADD COLUMN comments_revision INTEGER NOT NULL DEFAULT 0`)
|
||||
if err := r.migrate.Apply("task_plans.write_version", `ALTER TABLE task_plans ADD COLUMN write_version TEXT NOT NULL DEFAULT ''`); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.backfillTaskPlanWriteVersions(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Authoritative per-message change signal (chat render-perf). SQLite forbids a
|
||||
// non-constant default on ADD COLUMN, so the column is added nullable and
|
||||
@@ -418,6 +424,44 @@ func (r *Repository) runMigrations() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backfillTaskPlanWriteVersions assigns a fresh opaque token to legacy HEAD
|
||||
// rows. The empty-value predicate makes startup replay and an interrupted
|
||||
// backfill safe without changing an already assigned version.
|
||||
func (r *Repository) backfillTaskPlanWriteVersions() error {
|
||||
ctx := r.migrationContext()
|
||||
rows, err := r.db.QueryxContext(ctx, r.db.Rebind(`
|
||||
SELECT id FROM task_plans
|
||||
WHERE COALESCE(write_version, '') = ''
|
||||
`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("list plans missing write versions: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var planIDs []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return fmt.Errorf("scan plan missing write version: %w", err)
|
||||
}
|
||||
planIDs = append(planIDs, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return fmt.Errorf("iterate plans missing write versions: %w", err)
|
||||
}
|
||||
for _, id := range planIDs {
|
||||
if _, err := r.db.ExecContext(ctx, r.db.Rebind(`
|
||||
UPDATE task_plans
|
||||
SET write_version = ?
|
||||
WHERE id = ? AND COALESCE(write_version, '') = ''
|
||||
`), uuid.NewString(), id); err != nil {
|
||||
return fmt.Errorf("backfill plan write version %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) backfillTaskSessionQueueIncarnations() error {
|
||||
ctx := r.migrationContext()
|
||||
rows, err := r.db.QueryxContext(ctx, `SELECT id FROM task_sessions WHERE queue_incarnation_id = ''`)
|
||||
|
||||
Reference in New Issue
Block a user