feat(platform): show which startup step is running, with progress and ETA (#3813)

Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
Henry
2026-09-20 07:06:19 +08:00
committed by GitHub
parent 1727435c14
commit 8779c88d79
124 changed files with 7527 additions and 347 deletions
@@ -14,6 +14,7 @@ import (
"go.uber.org/zap"
"github.com/kandev/kandev/internal/db/dialect"
"github.com/kandev/kandev/internal/startup"
)
// migrateExecutorProfiles adds mcp_policy column and drops is_default from executor_profiles.
@@ -53,7 +54,9 @@ func (r *Repository) migrateSessionsAddCostColumns() {
}
// runMigrations applies idempotent ALTER TABLE migrations for schema evolution.
func (r *Repository) runMigrations() error {
//
//nolint:cyclop,funlen,maintidx // Legacy flat list of ~60 independent idempotent migration steps predating startup-step instrumentation; splitting it is out of scope here.
func (r *Repository) runMigrations(ctx context.Context) error {
if err := r.migrateTaskPriorityToTextPostgres(); err != nil {
return err
}
@@ -214,7 +217,9 @@ func (r *Repository) runMigrations() error {
// explicitly in CreateMessage/UpdateMessage. The backfill UPDATE is idempotent
// (WHERE updated_at IS NULL).
r.migrate.Apply("task_session_messages.updated_at", `ALTER TABLE task_session_messages ADD COLUMN updated_at TIMESTAMP`)
startup.BeginStep(ctx, startup.StepMessageTimestampsBackfill)
r.migrate.Apply("task_session_messages.updated_at.backfill", `UPDATE task_session_messages SET updated_at = created_at WHERE updated_at IS NULL`)
startup.EndStep(ctx, startup.StepMessageTimestampsBackfill)
r.migrate.Apply("idx_messages_session_updated", `CREATE INDEX IF NOT EXISTS idx_messages_session_updated ON task_session_messages(task_session_id, updated_at)`)
// task_session_commits gains a uniqueness constraint before its writer
@@ -378,7 +383,7 @@ func (r *Repository) runMigrations() error {
// table is not part of the supported upgrade path, so there is no
// intermediate-shape rebuild to run here. Only the historical-message
// backfill belongs in the migration phase.
r.migrateSubagentContextBackfill()
r.migrateSubagentContextBackfill(ctx)
// Durable per-session prompt ordinals. prompt_seq is allocated from a
// per-session sequence counter inside the create write boundary, so an
@@ -397,7 +402,7 @@ func (r *Repository) runMigrations() error {
task_session_id TEXT PRIMARY KEY,
last_seq INTEGER NOT NULL
)`)
if err := r.backfillPromptSeq(); err != nil {
if err := r.backfillPromptSeq(ctx); err != nil {
return err
}
@@ -504,7 +509,7 @@ func (r *Repository) backfillTaskSessionQueueIncarnations() error {
// session's sequence counter at its backfilled maximum. Idempotent: user rows
// already carrying a nonzero prompt_seq are untouched, and the counter seed
// ignores existing rows.
func (r *Repository) backfillPromptSeq() error {
func (r *Repository) backfillPromptSeq(ctx context.Context) error {
nmU := dialect.NormalizedMicrosecond(r.db.DriverName(), "u.created_at")
nmM := dialect.NormalizedMicrosecond(r.db.DriverName(), "task_session_messages.created_at")
update := fmt.Sprintf(`
@@ -516,7 +521,8 @@ func (r *Repository) backfillPromptSeq() error {
AND (%s < %s OR (%s = %s AND u.id <= task_session_messages.id))
)
WHERE author_type = 'user' AND prompt_seq = 0`, nmU, nmM, nmU, nmM)
ctx := r.migrationContext()
startup.BeginStep(ctx, startup.StepPromptSeqBackfill)
defer startup.EndStep(ctx, startup.StepPromptSeqBackfill)
if _, err := r.db.ExecContext(ctx, update); err != nil {
return fmt.Errorf("backfill prompt_seq: %w", err)
}