Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

287 lines · 21.3 KBRaw History
1/** SQLite schema of a repository Durable Object. Applied idempotently on construction. */
2export const REPO_SCHEMA = `
3CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
4CREATE TABLE IF NOT EXISTS refs (name TEXT PRIMARY KEY, oid TEXT NOT NULL);
5CREATE TABLE IF NOT EXISTS objects (
6 oid TEXT PRIMARY KEY, type INTEGER NOT NULL, size INTEGER NOT NULL, data BLOB, r2 INTEGER NOT NULL DEFAULT 0
7);
8CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL);
9-- Git LFS objects stored in R2 (repos/<id>/lfs/...), for the owner's storage quota (lib/quotas.ts).
10CREATE TABLE IF NOT EXISTS lfs_objects (oid TEXT PRIMARY KEY, size INTEGER NOT NULL);
11
12CREATE TABLE IF NOT EXISTS issues (
13 number INTEGER PRIMARY KEY, kind TEXT NOT NULL, title TEXT NOT NULL, body TEXT NOT NULL DEFAULT '',
14 state TEXT NOT NULL DEFAULT 'open', state_reason TEXT, author_id TEXT NOT NULL, author_login TEXT NOT NULL,
15 author_kind TEXT NOT NULL DEFAULT 'user', assignees TEXT NOT NULL DEFAULT '[]', labels TEXT NOT NULL DEFAULT '[]',
16 created_at TEXT NOT NULL, updated_at TEXT NOT NULL, closed_at TEXT, locked INTEGER NOT NULL DEFAULT 0
17);
18CREATE TABLE IF NOT EXISTS pulls (
19 number INTEGER PRIMARY KEY, head_ref TEXT NOT NULL, head_sha TEXT NOT NULL, base_ref TEXT NOT NULL,
20 draft INTEGER NOT NULL DEFAULT 0, merged INTEGER NOT NULL DEFAULT 0, merged_at TEXT, merged_by TEXT,
21 merge_commit TEXT, requested_reviewers TEXT NOT NULL DEFAULT '[]', intent TEXT
22);
23CREATE INDEX IF NOT EXISTS pulls_head ON pulls(head_ref);
24-- The last mergeability check of an open pull request (repo/collab.ts), for the base and head it saw:
25-- clean, conflicting, or in_base (its head is already part of the base branch). Today reads it (agentic/today-repo.ts).
26CREATE TABLE IF NOT EXISTS pull_merge_state (number INTEGER PRIMARY KEY, base_sha TEXT NOT NULL, head_sha TEXT NOT NULL, state TEXT NOT NULL);
27CREATE TABLE IF NOT EXISTS comments (
28 id TEXT PRIMARY KEY, number INTEGER NOT NULL, author_id TEXT NOT NULL, author_login TEXT NOT NULL,
29 author_kind TEXT NOT NULL DEFAULT 'user', body TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
30 review_id TEXT, path TEXT, line INTEGER, side TEXT, commit_id TEXT, in_reply_to TEXT, resolved INTEGER NOT NULL DEFAULT 0
31);
32CREATE INDEX IF NOT EXISTS comments_number ON comments(number);
33CREATE TABLE IF NOT EXISTS reviews (
34 id TEXT PRIMARY KEY, number INTEGER NOT NULL, author_id TEXT NOT NULL, author_login TEXT NOT NULL,
35 author_kind TEXT NOT NULL DEFAULT 'user', state TEXT NOT NULL, body TEXT NOT NULL DEFAULT '', commit_id TEXT NOT NULL,
36 submitted_at TEXT NOT NULL
37);
38CREATE INDEX IF NOT EXISTS reviews_number ON reviews(number);
39CREATE TABLE IF NOT EXISTS labels (name TEXT PRIMARY KEY, color TEXT NOT NULL, description TEXT NOT NULL DEFAULT '');
40CREATE TABLE IF NOT EXISTS protections (pattern TEXT PRIMARY KEY, rules TEXT NOT NULL);
41CREATE TABLE IF NOT EXISTS secrets (name TEXT PRIMARY KEY, ciphertext TEXT NOT NULL, updated_at TEXT NOT NULL);
42CREATE TABLE IF NOT EXISTS variables (name TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL);
43CREATE TABLE IF NOT EXISTS webhooks (
44 id TEXT PRIMARY KEY, url TEXT NOT NULL, secret TEXT NOT NULL, events TEXT NOT NULL, active INTEGER NOT NULL DEFAULT 1,
45 created_at TEXT NOT NULL, last_status INTEGER, last_delivery_at TEXT
46);
47CREATE TABLE IF NOT EXISTS checks (
48 id TEXT PRIMARY KEY, sha TEXT NOT NULL, name TEXT NOT NULL, status TEXT NOT NULL, conclusion TEXT,
49 run_id TEXT, job_id TEXT, details_url TEXT, output TEXT, app TEXT NOT NULL DEFAULT 'diffinery-actions',
50 started_at TEXT, completed_at TEXT
51);
52CREATE INDEX IF NOT EXISTS checks_sha ON checks(sha);
53CREATE TABLE IF NOT EXISTS runs (
54 id TEXT PRIMARY KEY, run_number INTEGER NOT NULL, workflow_path TEXT NOT NULL, workflow_name TEXT NOT NULL,
55 event TEXT NOT NULL, ref TEXT NOT NULL, sha TEXT NOT NULL, actor TEXT NOT NULL, status TEXT NOT NULL,
56 conclusion TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, pr_number INTEGER, attempt INTEGER NOT NULL DEFAULT 1,
57 event_payload TEXT NOT NULL, inputs TEXT NOT NULL DEFAULT '{}', concurrency_group TEXT, display_title TEXT NOT NULL DEFAULT ''
58);
59CREATE INDEX IF NOT EXISTS runs_created ON runs(created_at);
60CREATE TABLE IF NOT EXISTS jobs (
61 id TEXT PRIMARY KEY, run_id TEXT NOT NULL, job_key TEXT NOT NULL, name TEXT NOT NULL, status TEXT NOT NULL,
62 conclusion TEXT, matrix TEXT NOT NULL DEFAULT '{}', strategy TEXT NOT NULL DEFAULT '{}', started_at TEXT, completed_at TEXT,
63 outputs TEXT NOT NULL DEFAULT '{}', steps TEXT NOT NULL DEFAULT '[]', failure TEXT, token_hash TEXT, summary TEXT,
64 container TEXT, timeout_minutes INTEGER NOT NULL DEFAULT 360, labels TEXT
65);
66CREATE INDEX IF NOT EXISTS jobs_run ON jobs(run_id);
67CREATE TABLE IF NOT EXISTS logs (job_id TEXT NOT NULL, seq INTEGER NOT NULL, t TEXT NOT NULL, step INTEGER NOT NULL, text TEXT NOT NULL, PRIMARY KEY (job_id, seq));
68CREATE TABLE IF NOT EXISTS caches (key TEXT PRIMARY KEY, size INTEGER NOT NULL, created_at TEXT NOT NULL, last_used TEXT NOT NULL);
69-- Where older runners' artifacts were recorded before rt_artifacts; migrateRepoSchema moves them over.
70CREATE TABLE IF NOT EXISTS artifacts (id TEXT PRIMARY KEY, run_id TEXT NOT NULL, name TEXT NOT NULL, size INTEGER NOT NULL, created_at TEXT NOT NULL, UNIQUE(run_id, name));
71CREATE TABLE IF NOT EXISTS merge_queue (
72 number INTEGER PRIMARY KEY, base_ref TEXT NOT NULL, seq INTEGER NOT NULL, head_sha TEXT NOT NULL, method TEXT NOT NULL,
73 delete_branch INTEGER NOT NULL DEFAULT 0, enqueued_by TEXT NOT NULL, enqueued_at TEXT NOT NULL,
74 parent_sha TEXT, group_sha TEXT, group_ref TEXT, built_at TEXT
75);
76CREATE TABLE IF NOT EXISTS sandboxes (id TEXT PRIMARY KEY, secret TEXT NOT NULL, created_by TEXT NOT NULL, ref TEXT NOT NULL, created_at TEXT NOT NULL, status TEXT NOT NULL);
77
78-- Actions runtime services (repo/runtime.ts): the cache and artifact services the
79-- official toolkit talks to, with numeric ids like GitHub's. Content lives in R2.
80CREATE TABLE IF NOT EXISTS rt_caches (
81 id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, version TEXT NOT NULL, scope TEXT NOT NULL,
82 size INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL, job_id TEXT, created_at TEXT NOT NULL, last_used TEXT NOT NULL,
83 UNIQUE(key, version, scope)
84);
85CREATE INDEX IF NOT EXISTS rt_caches_used ON rt_caches(last_used);
86CREATE TABLE IF NOT EXISTS rt_artifacts (
87 id INTEGER PRIMARY KEY AUTOINCREMENT, run_id TEXT NOT NULL, job_id TEXT NOT NULL, name TEXT NOT NULL,
88 size INTEGER NOT NULL DEFAULT 0, digest TEXT, mime_type TEXT NOT NULL DEFAULT 'application/zip', status TEXT NOT NULL,
89 created_at TEXT NOT NULL, updated_at TEXT NOT NULL, expires_at TEXT NOT NULL
90);
91CREATE INDEX IF NOT EXISTS rt_artifacts_run ON rt_artifacts(run_id, name);
92-- A partial re-run's attempt lists the artifacts of the jobs it kept by their job (repo/runtime.ts runArtifacts).
93CREATE INDEX IF NOT EXISTS rt_artifacts_job ON rt_artifacts(job_id);
94-- What a job's GITHUB_TOKEN may do, from the workflow's permissions (repo/runtime.ts).
95CREATE TABLE IF NOT EXISTS job_grants (job_id TEXT PRIMARY KEY, role TEXT NOT NULL, permissions TEXT NOT NULL);
96-- GitHub-compatible extras (see ./releases.ts, ./statuses.ts, ./deployments.ts, ./social.ts).
97CREATE TABLE IF NOT EXISTS releases (
98 id INTEGER PRIMARY KEY AUTOINCREMENT, tag_name TEXT NOT NULL, target_commitish TEXT NOT NULL, name TEXT NOT NULL DEFAULT '',
99 body TEXT NOT NULL DEFAULT '', draft INTEGER NOT NULL DEFAULT 0, prerelease INTEGER NOT NULL DEFAULT 0,
100 author_id TEXT NOT NULL, author_login TEXT NOT NULL, author_kind TEXT NOT NULL DEFAULT 'user',
101 created_at TEXT NOT NULL, published_at TEXT, updated_at TEXT NOT NULL
102);
103CREATE INDEX IF NOT EXISTS releases_tag ON releases(tag_name);
104CREATE TABLE IF NOT EXISTS release_assets (
105 id INTEGER PRIMARY KEY AUTOINCREMENT, release_id INTEGER NOT NULL, name TEXT NOT NULL, label TEXT NOT NULL DEFAULT '',
106 content_type TEXT NOT NULL, size INTEGER NOT NULL DEFAULT 0, state TEXT NOT NULL DEFAULT 'starter', download_count INTEGER NOT NULL DEFAULT 0,
107 uploader_id TEXT, uploader_login TEXT, uploader_kind TEXT, r2_key TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
108 UNIQUE (release_id, name)
109);
110CREATE TABLE IF NOT EXISTS statuses (
111 id INTEGER PRIMARY KEY AUTOINCREMENT, sha TEXT NOT NULL, state TEXT NOT NULL, context TEXT NOT NULL, description TEXT, target_url TEXT,
112 creator_id TEXT NOT NULL, creator_login TEXT NOT NULL, creator_kind TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
113);
114CREATE INDEX IF NOT EXISTS statuses_sha ON statuses(sha, context);
115CREATE TABLE IF NOT EXISTS deployments (
116 id INTEGER PRIMARY KEY AUTOINCREMENT, sha TEXT NOT NULL, ref TEXT NOT NULL, task TEXT NOT NULL DEFAULT 'deploy', environment TEXT NOT NULL,
117 original_environment TEXT NOT NULL, description TEXT, payload TEXT NOT NULL DEFAULT '{}', transient INTEGER NOT NULL DEFAULT 0,
118 production INTEGER NOT NULL DEFAULT 0, creator_id TEXT NOT NULL, creator_login TEXT NOT NULL, creator_kind TEXT NOT NULL,
119 job_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
120);
121CREATE INDEX IF NOT EXISTS deployments_env ON deployments(environment);
122CREATE TABLE IF NOT EXISTS deployment_statuses (
123 id INTEGER PRIMARY KEY AUTOINCREMENT, deployment_id INTEGER NOT NULL, state TEXT NOT NULL, description TEXT NOT NULL DEFAULT '',
124 environment TEXT NOT NULL, environment_url TEXT NOT NULL DEFAULT '', log_url TEXT NOT NULL DEFAULT '',
125 creator_id TEXT NOT NULL, creator_login TEXT NOT NULL, creator_kind TEXT NOT NULL, created_at TEXT NOT NULL
126);
127CREATE INDEX IF NOT EXISTS deployment_statuses_dep ON deployment_statuses(deployment_id);
128CREATE TABLE IF NOT EXISTS environments (
129 id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE COLLATE NOCASE, branch_policy TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
130);
131-- Required reviewers' decisions on a run's environments (repo/deployments.ts): one approval per run and environment is enough.
132CREATE TABLE IF NOT EXISTS deployment_reviews (
133 id INTEGER PRIMARY KEY AUTOINCREMENT, run_id TEXT NOT NULL, environment TEXT NOT NULL COLLATE NOCASE, state TEXT NOT NULL, comment TEXT NOT NULL DEFAULT '',
134 user_id TEXT NOT NULL, user_login TEXT NOT NULL, created_at TEXT NOT NULL
135);
136CREATE INDEX IF NOT EXISTS deployment_reviews_run ON deployment_reviews(run_id);
137CREATE TABLE IF NOT EXISTS environment_secrets (environment TEXT NOT NULL COLLATE NOCASE, name TEXT NOT NULL, ciphertext TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (environment, name));
138CREATE TABLE IF NOT EXISTS environment_variables (environment TEXT NOT NULL COLLATE NOCASE, name TEXT NOT NULL, value TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (environment, name));
139CREATE TABLE IF NOT EXISTS reactions (
140 id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT NOT NULL, content TEXT NOT NULL, user_id TEXT NOT NULL, user_login TEXT NOT NULL,
141 user_kind TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE (subject, content, user_id)
142);
143CREATE TABLE IF NOT EXISTS issue_events (
144 id INTEGER PRIMARY KEY AUTOINCREMENT, number INTEGER NOT NULL, event TEXT NOT NULL, actor_id TEXT NOT NULL, actor_login TEXT NOT NULL,
145 actor_kind TEXT NOT NULL, detail TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL
146);
147CREATE INDEX IF NOT EXISTS issue_events_number ON issue_events(number);
148-- Who last moved each ref: tags made by agents never match environment tag patterns
149-- (./deployments.ts), and runs of code an agent pushed act as the agent (./token-policy.ts).
150CREATE TABLE IF NOT EXISTS ref_actors (ref TEXT PRIMARY KEY, actor_id TEXT NOT NULL, actor_kind TEXT NOT NULL, updated_at TEXT NOT NULL);
151-- Commits an agent pushed that aren't on the default branch yet: a run of code that includes one acts
152-- as an agent, whoever pushed last or made the tag (./token-policy.ts). Dropped once they land.
153CREATE TABLE IF NOT EXISTS agent_commits (oid TEXT PRIMARY KEY, pushed_at TEXT NOT NULL);
154-- GitHub imports and milestones (see ./imports.ts).
155CREATE TABLE IF NOT EXISTS milestones (
156 number INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', state TEXT NOT NULL DEFAULT 'open',
157 due_on TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, closed_at TEXT, creator TEXT
158);
159CREATE TABLE IF NOT EXISTS issue_milestones (number INTEGER PRIMARY KEY, milestone INTEGER NOT NULL);
160CREATE INDEX IF NOT EXISTS issue_milestones_milestone ON issue_milestones(milestone);
161CREATE TABLE IF NOT EXISTS import_findings (blob TEXT NOT NULL, kind TEXT NOT NULL, at TEXT NOT NULL, PRIMARY KEY (blob, kind));
162-- Refs a running import wrote, with the oid it set, so a retry only moves refs it owns (never a ref people adopted).
163CREATE TABLE IF NOT EXISTS import_refs (ref TEXT PRIMARY KEY, oid TEXT NOT NULL, import_id TEXT NOT NULL);
164-- A copy of the owning organization's Actions variables (D1 org_variables), kept in sync by the Directory.
165CREATE TABLE IF NOT EXISTS org_variables (name TEXT PRIMARY KEY, value TEXT NOT NULL);
166-- Self-hosted runners registered with a registration token (repo/actions.ts registerRunner). A runner
167-- claims jobs with its own token, never a person's; an ephemeral one's token ends with its first job.
168CREATE TABLE IF NOT EXISTS runners (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, labels TEXT NOT NULL, token_hash TEXT, ephemeral INTEGER NOT NULL DEFAULT 0, created_by TEXT NOT NULL, created_at TEXT NOT NULL, last_seen_at TEXT);
169CREATE TABLE IF NOT EXISTS runner_registrations (token_hash TEXT PRIMARY KEY, created_by TEXT NOT NULL, expires_at TEXT NOT NULL);
170`;
171
172/** Columns added after a table was first created; added with ALTER TABLE when missing. */
173const ADDED_COLUMNS: Array<[table: string, column: string, definition: string]> = [
174 // The commit a run reports its checks on: the PR head for pull_request runs, whose
175 // GITHUB_SHA is the test merge commit. Null means the run's own sha.
176 ["runs", "head_sha", "TEXT"],
177 // 1 when a webhook's secret is an encrypted envelope (repo/collab.ts); older rows hold it in plaintext until their next delivery.
178 ["webhooks", "secret_encrypted", "INTEGER NOT NULL DEFAULT 0"],
179 // 1 when the code a run executes came from an agent (repo/actions.ts agentOrigin): its jobs act as an agent.
180 ["runs", "agent_origin", "INTEGER NOT NULL DEFAULT 0"],
181 // The repository a pull request's head branch lives in, when it is not this one (a fork).
182 ["pulls", "head_repo_id", "TEXT"],
183 // Bytes an upload in progress has staged (counted against the budgets), and the
184 // committed object's etag (a later overwrite of the object is refused).
185 ["rt_caches", "staged", "INTEGER NOT NULL DEFAULT 0"],
186 ["rt_caches", "etag", "TEXT"],
187 ["rt_artifacts", "staged", "INTEGER NOT NULL DEFAULT 0"],
188 ["rt_artifacts", "etag", "TEXT"],
189 // 1 for an artifact an older runner uploaded through the runner API: a tarball at its legacy key (repo/runtime.ts).
190 ["rt_artifacts", "legacy", "INTEGER NOT NULL DEFAULT 0"],
191 // Who created a check run over the REST API (null for Diffinery Actions), and GitHub's numeric id.
192 ["checks", "creator_id", "TEXT"],
193 ["checks", "creator_login", "TEXT"],
194 ["checks", "creator_kind", "TEXT"],
195 ["checks", "seq", "INTEGER"],
196 // Per matrix leg: job-level continue-on-error, and the job-level concurrency group with its cancel-in-progress.
197 ["jobs", "continue_on_error", "INTEGER NOT NULL DEFAULT 0"],
198 ["jobs", "concurrency_group", "TEXT"],
199 ["jobs", "concurrency_cancel", "INTEGER NOT NULL DEFAULT 0"],
200 // Characters of log text stored so far (repo/actions.ts MAX_JOB_LOG).
201 ["jobs", "log_chars", "INTEGER NOT NULL DEFAULT 0"],
202 // 1 while a queued job waits for one of the repository's hosted runner slots (an account's share, lib/quotas.ts fleetLimits).
203 ["jobs", "fleet_held", "INTEGER NOT NULL DEFAULT 0"],
204 // Times the platform stopped the job's container before its runner started (repo/actions.ts QUICK_RETRY_SECONDS).
205 ["jobs", "early_stops", "INTEGER NOT NULL DEFAULT 0"],
206 // Runners on the previous image turned away during a rollout, and when the job starts again (repo/actions.ts STALE_IMAGE_TRIES).
207 ["jobs", "stale_tries", "INTEGER NOT NULL DEFAULT 0"],
208 ["jobs", "image_retry_at", "INTEGER"],
209 // The registered self-hosted runner that claimed the job (null for hosted jobs and personal-token claims).
210 ["jobs", "runner_id", "INTEGER"],
211 // Times a hosted job's container couldn't get a machine and the job went back in line (repo/actions.ts MAX_PLACEMENT_TRIES).
212 ["jobs", "placement_tries", "INTEGER NOT NULL DEFAULT 0"],
213 // What the runner reported when it fetched the job (JSON RunnerInfo: image, location, instance), for investigations.
214 ["jobs", "runner_info", "TEXT"],
215 // Times the platform replaced a hosted job's runner before its steps ran and the job started again (repo/actions.ts MAX_RUNNER_RESTARTS).
216 ["jobs", "restarts", "INTEGER NOT NULL DEFAULT 0"],
217 // Hosted runner size (runner/sizes.ts): the one runs-on asks for (null: chosen from the job's earlier runs),
218 // the log line saying which size the job got and why, and what the job used of its runner (JSON JobUsage).
219 ["jobs", "runner_size", "TEXT"],
220 ["jobs", "size_note", "TEXT"],
221 ["jobs", "usage", "TEXT"],
222 // When a running hosted job was cancelled and why, while its runner runs the steps that run after a cancel (repo/actions.ts CANCEL_STOP_MS).
223 ["jobs", "cancel_at", "INTEGER"],
224 ["jobs", "cancel_reason", "TEXT"],
225 // The GitHub import that wrote this row (null for everything else); a retry may only overwrite its own rows.
226 ["issues", "imported", "TEXT"],
227 ["pulls", "imported", "TEXT"],
228 ["comments", "imported", "TEXT"],
229 ["reviews", "imported", "TEXT"],
230 ["labels", "imported", "TEXT"],
231 ["milestones", "imported", "TEXT"],
232 ["protections", "imported", "TEXT"],
233 // Auto-merge while it is on (repo/auto-merge.ts AutoMergeState as JSON); null when off.
234 ["pulls", "auto_merge", "TEXT"],
235 ["releases", "imported", "TEXT"],
236 // "task" for an agent task's sandbox (agentic/): no public sandbox call reaches it, and its git
237 // credential also needs the person who started the task (task_person) to still be allowed.
238 ["sandboxes", "kind", "TEXT NOT NULL DEFAULT 'api'"],
239 ["sandboxes", "task_person", "TEXT"],
240 // Environment protection rules (repo/deployments.ts): required reviewers as JSON [{type: "User", id, login}],
241 // a wait timer in minutes, and whether whoever started a run may approve it.
242 ["environments", "reviewers", "TEXT"],
243 ["environments", "wait_timer", "INTEGER NOT NULL DEFAULT 0"],
244 ["environments", "prevent_self_review", "INTEGER NOT NULL DEFAULT 0"],
245 // The job's `environment:` name, evaluated when it is launched; runnerSpec uses this, never a second evaluation.
246 ["jobs", "environment", "TEXT"],
247 // While a job waits on its environment's protection rules: when it started waiting, and "released" once they passed.
248 ["jobs", "gate_since", "TEXT"],
249 ["jobs", "gate_state", "TEXT"],
250 // Logins that started earlier attempts of a re-run (JSON list): prevent_self_review covers them too.
251 ["runs", "prior_actors", "TEXT"],
252 // What a partial re-run (failed jobs, or one job) keeps from the attempt it re-runs (JSON RerunCarry in
253 // repo/actions.ts): per job key, the earlier attempt's job rows copied into this one instead of running.
254 ["runs", "rerun_carry", "TEXT"],
255 // Commits a run's remote reusable workflow calls resolved to at plan time (JSON, "repoId@ref" → sha).
256 ["runs", "call_refs", "TEXT"],
257 // A job copied unchanged from an earlier attempt by a partial re-run: the job that actually ran, whose log it shows.
258 ["jobs", "carried_from", "TEXT"],
259 // Bytes an object takes as stored (compressed, in SQLite or R2); null in objects stored before it was kept.
260 ["objects", "stored", "INTEGER"],
261 // A comment hidden (minimized) by someone with write access: GitHub's reason (spam, abuse, off_topic, outdated,
262 // duplicate, resolved), who hid it and when (lib/moderation.ts). Null while it is shown.
263 ["comments", "minimized_reason", "TEXT"],
264 ["comments", "minimized_by", "TEXT"],
265 ["comments", "minimized_at", "TEXT"],
266 // When the job's current token was issued (repo/actions.ts jobTokenExpiry); a job not yet started counts from it.
267 ["jobs", "token_at", "TEXT"],
268];
269
270export function migrateRepoSchema(sql: SqlStorage): void {
271 for (const [table, column, definition] of ADDED_COLUMNS) {
272 const has = sql.exec<{ n: number }>(`SELECT count(*) AS n FROM pragma_table_info('${table}') WHERE name = ?`, column).one().n > 0;
273 if (has) continue;
274 sql.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
275 // Job tokens issued before token_at was recorded count from this upgrade, so they still expire.
276 if (table === "jobs" && column === "token_at") sql.exec("UPDATE jobs SET token_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE token_hash IS NOT NULL");
277 }
278 // Older runners' artifacts were first recorded in `artifacts`, which the REST API didn't list: they
279 // move to rt_artifacts, kept for 90 days from their upload. One shadowed by an artifact service
280 // upload of the same name was never shown and is dropped (its object goes with the bucket's age rule).
281 sql.exec(
282 `INSERT INTO rt_artifacts (run_id, job_id, name, size, mime_type, status, created_at, updated_at, expires_at, legacy)
283 SELECT a.run_id, '', a.name, a.size, 'application/gzip', 'ready', a.created_at, a.created_at, strftime('%Y-%m-%dT%H:%M:%fZ', a.created_at, '+90 days'), 1 FROM artifacts a
284 WHERE NOT EXISTS (SELECT 1 FROM rt_artifacts r WHERE r.run_id = a.run_id AND r.name = a.name)`,
285 );
286 sql.exec("DELETE FROM artifacts");
287}