123456789101112131415161718192021222324252627BEGIN;2829CREATE TABLE schema_version (30 version INTEGER NOT NULL31);32INSERT INTO schema_version (version) VALUES (1);33343536CREATE TABLE repos (37 name TEXT PRIMARY KEY,38 created_at BIGINT NOT NULL,39 description TEXT,40 languages TEXT DEFAULT '',41 details TEXT,42 categories TEXT DEFAULT '',43 count_extensions TEXT DEFAULT '',44 contributor_handle TEXT,45 issues_enabled BIGINT NOT NULL DEFAULT 1,46 languages_cloud TEXT,47 project_id TEXT,48 trusted BIGINT NOT NULL DEFAULT 0,49 trust_computed_at BIGINT,50 private BIGINT NOT NULL DEFAULT 0,51 builds_enabled BIGINT NOT NULL DEFAULT 1,52 display_languages TEXT53);5455CREATE TABLE saves (56 id TEXT NOT NULL,57 repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,58 lane TEXT NOT NULL CHECK (lane IN ('main','dev')),59 message TEXT NOT NULL,60 timestamp BIGINT NOT NULL,61 size_bytes BIGINT NOT NULL,62 parent_id TEXT,63 is_checkpoint BIGINT NOT NULL DEFAULT 0,64 checksum TEXT NOT NULL,65 manifest_path TEXT NOT NULL,66 file_count BIGINT NOT NULL DEFAULT 0,67 line_count BIGINT NOT NULL DEFAULT 0,68 session TEXT,69 agent TEXT,70 PRIMARY KEY (id, repo)71);72CREATE INDEX saves_repo_lane_ts ON saves(repo, lane, timestamp);7374CREATE TABLE object_refs (75 hex_hash TEXT NOT NULL,76 save_id TEXT NOT NULL,77 repo TEXT NOT NULL,78 PRIMARY KEY (hex_hash, save_id, repo)79);80CREATE INDEX object_refs_hash ON object_refs(hex_hash);81828384858687888990919293949596979899100101CREATE TABLE object_line_counts (102 hex_hash TEXT PRIMARY KEY,103 lines BIGINT NOT NULL104);105106107108109110CREATE TABLE repo_purged_paths (111 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,112 path TEXT NOT NULL,113 purged_at BIGINT NOT NULL,114 PRIMARY KEY (repo_id, path)115);116117CREATE TABLE offer_tokens (118 offer_id TEXT PRIMARY KEY,119 repo TEXT NOT NULL,120 manifest_id TEXT NOT NULL,121 missing TEXT NOT NULL,122 manifest TEXT NOT NULL,123 key_type TEXT NOT NULL CHECK (key_type IN ('operator','contributor')),124 expires_at BIGINT NOT NULL,125 126 127 negotiated_account TEXT128);129130CREATE TABLE pending_offers (131 offer_id TEXT PRIMARY KEY,132 repo TEXT NOT NULL,133 manifest_id TEXT NOT NULL,134 manifest TEXT NOT NULL,135 contributor TEXT,136 submitted_at BIGINT NOT NULL,137 status TEXT NOT NULL DEFAULT 'pending'138 CHECK (status IN ('draft','pending','accepted','declined')),139 reviewed_at BIGINT,140 review_note TEXT,141 agent_verdict TEXT,142 agent_status TEXT DEFAULT 'pending'143 CHECK (agent_status IN ('pending','running','complete','failed','unavailable')),144 findings_json TEXT,145 scan_status TEXT CHECK (scan_status IN ('ok','failed')),146 147 148 149 description TEXT150);151152CREATE TABLE pending_offer_identity_quarantine (153 offer_id TEXT PRIMARY KEY REFERENCES pending_offers(offer_id) ON DELETE CASCADE,154 legacy_fingerprint TEXT NOT NULL,155 reason TEXT NOT NULL,156 recorded_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT157);158CREATE INDEX pending_offer_identity_quarantine_fp159 ON pending_offer_identity_quarantine(legacy_fingerprint);160161CREATE TABLE steals (162 id TEXT PRIMARY KEY,163 repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,164 timestamp BIGINT NOT NULL165);166CREATE INDEX steals_repo_ts ON steals(repo, timestamp);167168CREATE TABLE storage_snapshots (169 date TEXT PRIMARY KEY,170 total_bytes BIGINT NOT NULL,171 object_count BIGINT NOT NULL,172 repo_count BIGINT NOT NULL,173 save_count BIGINT NOT NULL,174 languages TEXT,175 first_received BIGINT176);177178CREATE TABLE instance_images (179 filename TEXT PRIMARY KEY,180 created_at BIGINT NOT NULL,181 size_bytes BIGINT NOT NULL,182 checksum TEXT NOT NULL,183 repo_count BIGINT NOT NULL,184 save_count BIGINT NOT NULL,185 kepr_version TEXT NOT NULL186);187188CREATE TABLE diff_cache (189 repo TEXT NOT NULL,190 save_id TEXT NOT NULL,191 diff_json TEXT NOT NULL,192 computed_at BIGINT NOT NULL,193 PRIMARY KEY (repo, save_id)194);195196CREATE TABLE ops_credentials (197 key TEXT PRIMARY KEY,198 value TEXT NOT NULL199);200201202203CREATE TABLE handles (204 id TEXT PRIMARY KEY,205 handle TEXT UNIQUE NOT NULL,206 created_at BIGINT NOT NULL,207 recovery_hash TEXT NOT NULL208);209210211212CREATE TABLE repo_assets (213 id TEXT PRIMARY KEY,214 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,215 filename TEXT NOT NULL,216 mime_type TEXT NOT NULL,217 size_bytes BIGINT NOT NULL,218 uploaded_at BIGINT NOT NULL219);220221CREATE TABLE repo_dependencies (222 from_repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,223 to_repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,224 first_stolen_at BIGINT NOT NULL,225 last_stolen_at BIGINT NOT NULL,226 PRIMARY KEY (from_repo, to_repo)227);228229CREATE TABLE build_runs (230 id TEXT PRIMARY KEY,231 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,232 save_id TEXT NOT NULL,233 version TEXT NOT NULL,234 trigger TEXT NOT NULL,235 status TEXT NOT NULL,236 started_at BIGINT NOT NULL,237 finished_at BIGINT,238 log TEXT,239 release_notes TEXT,240 platform_results TEXT NOT NULL DEFAULT '{}',241 duration_ms BIGINT,242 lane TEXT NOT NULL DEFAULT 'main'243);244245CREATE TABLE release_artifacts (246 id TEXT PRIMARY KEY,247 build_run TEXT NOT NULL REFERENCES build_runs(id),248 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,249 version TEXT NOT NULL,250 platform TEXT NOT NULL,251 filename TEXT NOT NULL,252 size_bytes BIGINT NOT NULL,253 sha256 TEXT NOT NULL,254 blake3 TEXT NOT NULL,255 signature TEXT NOT NULL DEFAULT '',256 built_at BIGINT NOT NULL257);258CREATE UNIQUE INDEX release_artifacts_repo_version_platform259 ON release_artifacts (repo_id, version, platform);260261262263CREATE TABLE issues (264 id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,265 repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,266 title TEXT NOT NULL,267 description TEXT NOT NULL,268 contact TEXT,269 filed_at BIGINT NOT NULL,270 filed_save TEXT NOT NULL,271 filed_ver TEXT,272 status TEXT NOT NULL DEFAULT 'open',273 closed_at BIGINT,274 closed_save TEXT,275 ip_hash TEXT,276 number BIGINT NOT NULL DEFAULT 0,277 type TEXT NOT NULL DEFAULT 'bug',278 severity TEXT,279 face_id TEXT,280 resolution_face TEXT,281 account_id TEXT,282 contact_email TEXT,283 updated_at BIGINT NOT NULL DEFAULT 0,284 reporter_name TEXT285);286CREATE UNIQUE INDEX issues_repo_number ON issues(repo, number);287CREATE INDEX issues_repo_status_created288 ON issues(repo, status, filed_at DESC);289CREATE INDEX issues_repo_type_status_created290 ON issues(repo, type, status, filed_at DESC);291292CREATE TABLE issue_notes (293 id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,294 issue_id BIGINT NOT NULL REFERENCES issues(id),295 body TEXT NOT NULL,296 posted_at BIGINT NOT NULL,297 is_op BIGINT NOT NULL DEFAULT 0298);299300CREATE TABLE issue_entries (301 id TEXT PRIMARY KEY,302 issue_id BIGINT NOT NULL REFERENCES issues(id) ON DELETE CASCADE,303 account_id TEXT,304 entry_type TEXT NOT NULL CHECK (entry_type IN ('reply','status_change','face_linked')),305 body TEXT,306 created_at BIGINT NOT NULL307);308CREATE INDEX issue_entries_issue ON issue_entries(issue_id, created_at);309310CREATE TABLE issue_attachments (311 id TEXT PRIMARY KEY,312 issue_id BIGINT REFERENCES issues(id) ON DELETE CASCADE,313 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,314 filename TEXT NOT NULL,315 mime_type TEXT NOT NULL,316 size_bytes BIGINT NOT NULL,317 path TEXT NOT NULL,318 tmp_token TEXT,319 tmp_expires BIGINT,320 created_at BIGINT NOT NULL321);322CREATE INDEX issue_attachments_issue ON issue_attachments(issue_id, created_at);323CREATE INDEX issue_attachments_repo ON issue_attachments(repo_id, created_at);324325326327CREATE TABLE graph_cache (328 repo TEXT NOT NULL,329 head_save_id TEXT NOT NULL,330 json TEXT NOT NULL,331 computed_at BIGINT NOT NULL,332 PRIMARY KEY (repo, head_save_id)333);334335CREATE TABLE archive_cache (336 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,337 save_id TEXT NOT NULL,338 path TEXT NOT NULL,339 size_bytes BIGINT NOT NULL,340 created_at BIGINT NOT NULL,341 342 343 344 345 nar_hash TEXT,346 PRIMARY KEY (repo_id, save_id)347);348349CREATE TABLE archive_hashes (350 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,351 save_id TEXT NOT NULL,352 blake3 TEXT NOT NULL,353 sha256 TEXT NOT NULL,354 nar_hash TEXT,355 computed_at BIGINT NOT NULL,356 PRIMARY KEY (repo_id, save_id)357);358359CREATE TABLE pow_challenges (360 id TEXT PRIMARY KEY,361 nonce TEXT NOT NULL,362 difficulty BIGINT NOT NULL DEFAULT 4,363 created_at BIGINT NOT NULL,364 expires_at BIGINT NOT NULL,365 used BIGINT NOT NULL DEFAULT 0366);367368369370CREATE TABLE accounts (371 id TEXT PRIMARY KEY,372 username TEXT NOT NULL UNIQUE,373 bio TEXT,374 location TEXT,375 website TEXT,376 email TEXT,377 recovery_hash TEXT NOT NULL,378 created_at BIGINT NOT NULL,379 tier TEXT NOT NULL DEFAULT 'contributor'380 CHECK (tier IN ('operator', 'team', 'contributor')),381 382 383 password_hash TEXT,384 totp_secret TEXT,385 notif_offer_accepted BIGINT NOT NULL DEFAULT 1,386 notif_offer_rejected BIGINT NOT NULL DEFAULT 1,387 notif_issue_response BIGINT NOT NULL DEFAULT 1,388 notif_issue_resolved BIGINT NOT NULL DEFAULT 1,389 notif_issue_status BIGINT NOT NULL DEFAULT 1390);391CREATE INDEX accounts_username ON accounts(username);392393394395396397CREATE UNIQUE INDEX accounts_email_unique ON accounts(email) WHERE email IS NOT NULL;398399400401402403404405406CREATE TABLE tickets (407 id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,408 title TEXT NOT NULL,409 body TEXT NOT NULL DEFAULT '',410 state TEXT NOT NULL DEFAULT 'open'411 CHECK (state IN ('open', 'in_progress', 'closed')),412 repo TEXT REFERENCES repos(name) ON UPDATE CASCADE ON DELETE SET NULL,413 resolving_save_id TEXT,414 created_by TEXT NOT NULL REFERENCES accounts(id),415 assigned_to TEXT REFERENCES accounts(id) ON DELETE SET NULL,416 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,417 updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,418 closed_at BIGINT419);420CREATE INDEX tickets_state ON tickets(state, created_at DESC);421CREATE INDEX tickets_repo ON tickets(repo);422CREATE INDEX tickets_assigned ON tickets(assigned_to);423CREATE INDEX tickets_created_by ON tickets(created_by);424425CREATE TABLE ticket_attachments (426 id TEXT PRIMARY KEY,427 ticket_id BIGINT REFERENCES tickets(id) ON DELETE CASCADE,428 repo_id TEXT REFERENCES repos(name) ON UPDATE CASCADE,429 filename TEXT NOT NULL,430 mime_type TEXT NOT NULL,431 size_bytes BIGINT NOT NULL,432 path TEXT NOT NULL,433 created_at BIGINT NOT NULL434);435CREATE INDEX ticket_attachments_ticket ON ticket_attachments(ticket_id, created_at);436CREATE INDEX ticket_attachments_repo ON ticket_attachments(repo_id, created_at);437438439440441442443444445446447448CREATE TABLE save_comments (449 id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,450 save_id TEXT NOT NULL,451 repo TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE ON DELETE CASCADE,452 author_account_id TEXT NOT NULL REFERENCES accounts(id),453 body TEXT NOT NULL,454 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,455 updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,456 edited BIGINT NOT NULL DEFAULT 0,457 FOREIGN KEY (save_id, repo) REFERENCES saves(id, repo) ON DELETE CASCADE458);459CREATE INDEX save_comments_save_id ON save_comments(save_id, created_at);460CREATE INDEX save_comments_repo ON save_comments(repo, created_at DESC);461CREATE INDEX save_comments_author ON save_comments(author_account_id);462463CREATE TABLE save_acknowledgements (464 save_id TEXT NOT NULL,465 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,466 kind TEXT NOT NULL DEFAULT 'ack'467 CHECK (kind IN ('ack', 'flag')),468 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,469 PRIMARY KEY (save_id, account_id)470);471CREATE INDEX save_acks_save_id ON save_acknowledgements(save_id);472473CREATE TABLE draft_comments (474 id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,475 offer_id TEXT NOT NULL REFERENCES pending_offers(offer_id) ON DELETE CASCADE,476 author_account_id TEXT NOT NULL REFERENCES accounts(id),477 body TEXT NOT NULL,478 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT479);480CREATE INDEX draft_comments_offer ON draft_comments(offer_id, created_at);481482CREATE TABLE notifications (483 id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,484 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,485 kind TEXT NOT NULL CHECK (kind IN (486 'save_comment', 'save_comment_reply', 'save_mention', 'save_ack',487 'save_flag', 'contribution_accepted', 'contribution_declined',488 'contribution_pending', 'draft_comment', 'issue_filed', 'issue_reply',489 'issue_response', 'issue_resolved', 'issue_status',490 'ticket_assigned', 'ticket_state_changed', 'team_promoted',491 'milestone_saves', 'milestone_downloads', 'milestone_first_steal',492 'milestone_first_contributor', 'milestone_trusted'493 )),494 payload_json TEXT NOT NULL DEFAULT '{}',495 preview TEXT NOT NULL DEFAULT '',496 href TEXT NOT NULL DEFAULT '/',497 read BIGINT NOT NULL DEFAULT 0,498 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,499 read_at BIGINT500);501CREATE INDEX notifications_account_unread ON notifications(account_id, read, created_at DESC);502CREATE INDEX notifications_account_all ON notifications(account_id, created_at DESC);503CREATE INDEX notifications_created_at ON notifications(created_at DESC);504505CREATE TABLE notification_preferences (506 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,507 kind TEXT NOT NULL,508 in_app BIGINT NOT NULL DEFAULT 1,509 email BIGINT NOT NULL DEFAULT 0,510 PRIMARY KEY (account_id, kind)511);512513CREATE TABLE notification_category_prefs (514 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,515 category TEXT NOT NULL CHECK (category IN (516 'contributions', 'discussion', 'issues', 'tickets', 'milestones'517 )),518 in_app BIGINT NOT NULL DEFAULT 1,519 email BIGINT NOT NULL DEFAULT 0,520 PRIMARY KEY (account_id, category)521);522523CREATE TABLE sessions (524 id TEXT PRIMARY KEY,525 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,526 created_at BIGINT NOT NULL,527 expires_at BIGINT NOT NULL,528 last_used BIGINT NOT NULL,529 user_agent TEXT530);531CREATE INDEX sessions_account ON sessions(account_id);532CREATE INDEX sessions_expiry ON sessions(expires_at);533534CREATE TABLE account_recovery_codes (535 id TEXT PRIMARY KEY,536 account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,537 code_hash TEXT NOT NULL,538 created_at BIGINT NOT NULL,539 expires_at BIGINT NOT NULL,540 used BIGINT NOT NULL DEFAULT 0541);542CREATE INDEX account_recovery_codes_account ON account_recovery_codes(account_id, expires_at);543544545546CREATE TABLE repo_health (547 repo_id TEXT PRIMARY KEY REFERENCES repos(name) ON UPDATE CASCADE,548 save_id TEXT NOT NULL,549 computed_at BIGINT NOT NULL,550 secrets_status TEXT NOT NULL DEFAULT 'clean',551 secrets_detail TEXT,552 deps_status TEXT NOT NULL DEFAULT 'none',553 deps_detail TEXT,554 builds_status TEXT NOT NULL DEFAULT 'none',555 builds_detail TEXT,556 agents_status TEXT NOT NULL DEFAULT 'none',557 agents_detail TEXT,558 license_status TEXT NOT NULL DEFAULT 'none',559 license_detail TEXT,560 trust_status TEXT NOT NULL DEFAULT 'clean',561 trust_detail TEXT,562 collapsed_html TEXT NOT NULL DEFAULT ''563);564565CREATE TABLE secret_scan_results (566 id TEXT PRIMARY KEY,567 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,568 save_id TEXT NOT NULL,569 file_path TEXT NOT NULL,570 line_number BIGINT,571 pattern TEXT NOT NULL,572 entropy DOUBLE PRECISION,573 severity TEXT NOT NULL,574 status TEXT NOT NULL,575 detected_at BIGINT NOT NULL576);577CREATE INDEX secret_scan_results_repo_save578 ON secret_scan_results(repo_id, save_id, detected_at DESC);579580CREATE TABLE secret_false_positives (581 repo_id TEXT NOT NULL,582 file_path TEXT NOT NULL,583 marked_at BIGINT NOT NULL,584 PRIMARY KEY (repo_id, file_path)585);586587CREATE TABLE code_finding_false_positives (588 repo_id TEXT NOT NULL,589 code TEXT NOT NULL,590 path TEXT NOT NULL,591 marked_at BIGINT NOT NULL,592 PRIMARY KEY (repo_id, code, path)593);594595596597598599CREATE TABLE safety_suppressions (600 id TEXT PRIMARY KEY,601 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE ON DELETE CASCADE,602 file_path TEXT NOT NULL,603 rule TEXT NOT NULL,604 fingerprint TEXT NOT NULL,605 dismissed_at BIGINT NOT NULL,606 dismissed_by TEXT NOT NULL REFERENCES accounts(id),607 note TEXT608);609CREATE INDEX safety_suppressions_repo ON safety_suppressions(repo_id, file_path, rule);610611CREATE TABLE dep_advisories (612 id TEXT PRIMARY KEY,613 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,614 save_id TEXT NOT NULL,615 dep_name TEXT NOT NULL,616 dep_version TEXT NOT NULL,617 cve_id TEXT,618 severity TEXT NOT NULL,619 summary TEXT NOT NULL,620 fix_version TEXT,621 detected_at BIGINT NOT NULL622);623CREATE INDEX dep_advisories_repo_save624 ON dep_advisories(repo_id, save_id, detected_at DESC);625626CREATE TABLE binary_sizes (627 id TEXT PRIMARY KEY,628 repo_id TEXT NOT NULL REFERENCES repos(name) ON UPDATE CASCADE,629 save_id TEXT NOT NULL,630 platform TEXT NOT NULL,631 size_bytes BIGINT NOT NULL,632 built_at BIGINT NOT NULL633);634CREATE INDEX binary_sizes_repo_save635 ON binary_sizes(repo_id, save_id, built_at DESC);636637CREATE TABLE trust_results (638 repo_id TEXT PRIMARY KEY REFERENCES repos(name) ON UPDATE CASCADE,639 save_id TEXT NOT NULL,640 trusted BIGINT NOT NULL,641 secrets_clean BIGINT NOT NULL,642 no_binary_blobs BIGINT NOT NULL,643 build_scripts_clean BIGINT NOT NULL,644 no_trojan_source BIGINT NOT NULL,645 supply_chain_stable BIGINT NOT NULL,646 licenses_compatible BIGINT NOT NULL,647 has_attribution BIGINT NOT NULL,648 detail_json TEXT NOT NULL DEFAULT '{}',649 computed_at BIGINT NOT NULL650);651652653654CREATE TABLE engagement_events (655 id TEXT PRIMARY KEY,656 event_type TEXT NOT NULL,657 repo_id TEXT,658 save_id TEXT,659 platform TEXT,660 occurred_at BIGINT NOT NULL661);662CREATE INDEX eng_repo_type ON engagement_events(repo_id, event_type, occurred_at DESC);663CREATE INDEX eng_type_time ON engagement_events(event_type, occurred_at DESC);664665CREATE TABLE notification_queue (666 id TEXT PRIMARY KEY,667 account_id TEXT,668 type TEXT NOT NULL,669 payload TEXT NOT NULL,670 status TEXT NOT NULL DEFAULT 'pending',671 attempts BIGINT NOT NULL DEFAULT 0,672 next_retry BIGINT,673 created_at BIGINT NOT NULL,674 sent_at BIGINT675);676CREATE INDEX notification_queue_pending677 ON notification_queue(status, next_retry);678679680681682683684CREATE TABLE jobs (685 id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,686 kind TEXT NOT NULL,687 payload_json TEXT NOT NULL DEFAULT '{}',688 status TEXT NOT NULL DEFAULT 'pending'689 CHECK (status IN ('pending','running','done','failed')),690 691 692 dedup_key TEXT,693 priority BIGINT NOT NULL DEFAULT 5,694 attempts BIGINT NOT NULL DEFAULT 0,695 max_attempts BIGINT NOT NULL DEFAULT 3,696 created_at BIGINT NOT NULL,697 started_at BIGINT,698 completed_at BIGINT,699 error_text TEXT,700 run_after BIGINT NOT NULL DEFAULT 0701);702703704CREATE INDEX jobs_pending ON jobs(priority, created_at)705 WHERE status = 'pending';706707708CREATE UNIQUE INDEX jobs_dedup ON jobs(kind, dedup_key)709 WHERE status IN ('pending','running') AND dedup_key IS NOT NULL;710711712CREATE INDEX jobs_completed_at ON jobs(completed_at)713 WHERE status IN ('done','failed');714715COMMIT;