1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-- Replace state/failure_kind columns with timestamp-based lifecycle and
-- outcome enum. Column renames:
--   queued_at_ms   → created_at
--   started_at_ms  → dispatched_at
--   finished_at_ms → resolved_at
--
-- Lifecycle is now derived from whether timestamps are set:
--   queued:   dispatched_at IS NULL AND resolved_at IS NULL
--   active:   dispatched_at IS NOT NULL AND resolved_at IS NULL
--   resolved: resolved_at IS NOT NULL (outcome IS NOT NULL)
--
-- outcome replaces state+failure_kind for resolved runs:
--   state='succeeded'                          → 'succeeded'
--   state='failed', failure_kind='orphaned'    → 'failed-orphaned'
--   state='failed', failure_kind='process-crashed' → 'failed-internal'
--   state='failed' (other)                     → 'failed-pipeline'
--   state='canceled'                           → 'superseded'

CREATE TABLE runs_new (
  id             TEXT    PRIMARY KEY,
  repo           TEXT    NOT NULL,
  ref_name       TEXT    NOT NULL,
  sha            TEXT    NOT NULL,
  pushed_at_ms   INTEGER NOT NULL,
  created_at     INTEGER NOT NULL,
  dispatched_at  INTEGER,
  resolved_at    INTEGER,
  outcome        TEXT,
  run_token      TEXT,
  git_dir        TEXT,
  traceparent    TEXT,

  CHECK (dispatched_at IS NULL OR dispatched_at >= created_at),
  CHECK (resolved_at   IS NULL OR resolved_at   >= created_at),
  CHECK (resolved_at   IS NULL OR dispatched_at IS NULL
         OR resolved_at >= dispatched_at),

  CHECK ((resolved_at IS NULL) = (outcome IS NULL)),

  CHECK (outcome IS NULL OR outcome IN (
    'succeeded',
    'failed-pipeline', 'failed-orphaned', 'failed-internal',
    'superseded'
  ))
);

INSERT INTO runs_new
  SELECT
    id, repo, ref_name, sha, pushed_at_ms,
    queued_at_ms,
    started_at_ms,
    finished_at_ms,
    CASE
      WHEN state IN ('queued', 'active') THEN NULL
      WHEN state = 'succeeded' THEN 'succeeded'
      WHEN state = 'failed' THEN
        CASE failure_kind
          WHEN 'orphaned'        THEN 'failed-orphaned'
          WHEN 'process-crashed' THEN 'failed-internal'
          ELSE                        'failed-pipeline'
        END
      WHEN state = 'canceled' THEN 'superseded'
      ELSE NULL
    END,
    run_token, git_dir, traceparent
  FROM runs;

DROP TABLE runs;
ALTER TABLE runs_new RENAME TO runs;

CREATE INDEX runs_repo_pushed_at ON runs(repo, pushed_at_ms DESC);
CREATE INDEX runs_pending ON runs(created_at) WHERE outcome IS NULL;