diff --git a/drizzle/0064_job_attempt_receipts.sql b/drizzle/0064_job_attempt_receipts.sql new file mode 100644 index 00000000..4f986403 --- /dev/null +++ b/drizzle/0064_job_attempt_receipts.sql @@ -0,0 +1,146 @@ +CREATE SEQUENCE job_attempt_receipt_ids AS bigint START 1 INCREMENT 1 CACHE 1 NO CYCLE; +--> statement-breakpoint +CREATE TABLE "job_attempt_receipts" ( + "receipt_id" bigint PRIMARY KEY NOT NULL, + "predecessor_id" bigint, + "source_epoch" uuid, + "schema_version" integer DEFAULT 1 NOT NULL, + "claim_receipt_id" bigint, + "claim_kind" text DEFAULT 'claim' NOT NULL, + "binding_receipt_id" bigint, + "binding_kind" text DEFAULT 'review_bound' NOT NULL, + "job_id" bigint, + "ordinal" integer, + "job_kind" text, + "review_id" bigint, + "review_public_id" uuid, + "event_kind" text NOT NULL, + "outcome" text, + "phase" text, + "category" text, + "recovered" boolean, + "recovery" text, + "occurred_at" timestamp with time zone DEFAULT clock_timestamp() NOT NULL, + CONSTRAINT "job_attempt_receipts_predecessor_id_unique" UNIQUE("predecessor_id"), + CONSTRAINT "job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_unique" UNIQUE("receipt_id","job_id","ordinal","job_kind","event_kind"), + CONSTRAINT "job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_unique" UNIQUE("receipt_id","claim_receipt_id","job_id","review_id","review_public_id","event_kind"), + CONSTRAINT "job_attempt_receipt_check_1" CHECK (schema_version = 1), + CONSTRAINT "job_attempt_receipt_check_2" CHECK (claim_kind = 'claim'), + CONSTRAINT "job_attempt_receipt_check_3" CHECK (binding_kind = 'review_bound'), + CONSTRAINT "job_attempt_receipt_check_4" CHECK (job_kind IN ('review','review-feedback')), + CONSTRAINT "job_attempt_receipt_check_5" CHECK (event_kind IN ('genesis','claim','review_bound','review_observed','incident','queue_closed')), + CONSTRAINT "job_attempt_receipt_check_6" CHECK (phase IN ('planner','review','scorer','respond')), + CONSTRAINT "job_attempt_receipt_check_7" CHECK (category IN ('providerError','invalidOutput','timeout','deadline')), + CONSTRAINT "job_attempt_receipt_check_8" CHECK (recovery IN ('repair','fallback')), + CONSTRAINT "job_attempt_receipt_check_9" CHECK (isfinite(occurred_at)), + CONSTRAINT "job_attempt_receipt_check_10" CHECK ( + (event_kind = 'genesis' AND receipt_id = 0 AND source_epoch IS NOT NULL + AND predecessor_id IS NULL AND claim_receipt_id IS NULL AND binding_receipt_id IS NULL + AND job_id IS NULL AND ordinal IS NULL AND job_kind IS NULL AND review_id IS NULL + AND review_public_id IS NULL AND outcome IS NULL AND phase IS NULL AND category IS NULL + AND recovered IS NULL AND recovery IS NULL) + OR + (event_kind <> 'genesis' AND receipt_id > 0 AND source_epoch IS NULL + AND predecessor_id IS NOT NULL AND predecessor_id < receipt_id + AND claim_receipt_id IS NOT NULL AND claim_receipt_id <= receipt_id + AND job_id IS NOT NULL AND job_id > 0 AND ordinal IS NOT NULL AND ordinal > 0 + AND job_kind IS NOT NULL AND outcome IS NOT NULL) + ), + CONSTRAINT "job_attempt_receipt_check_11" CHECK ((review_id IS NULL) = (review_public_id IS NULL)), + CONSTRAINT "job_attempt_receipt_check_12" CHECK (review_id IS NULL OR review_id > 0), + CONSTRAINT "job_attempt_receipt_check_13" CHECK ( + (event_kind = 'genesis') OR + (event_kind = 'claim' AND claim_receipt_id = receipt_id AND review_id IS NULL AND outcome = 'started') OR + (event_kind = 'review_bound' AND review_id IS NOT NULL AND outcome = 'bound') OR + (event_kind = 'review_observed' AND review_id IS NOT NULL AND outcome IN ('staged','completed','failed','interrupted','cancelled')) OR + (event_kind = 'incident' AND review_id IS NOT NULL AND outcome = 'observed') OR + (event_kind = 'queue_closed' AND review_id IS NULL AND outcome IN ('succeeded','retry','failed','continued','interrupted','cancelled','coalesced','deferred')) + ), + CONSTRAINT "job_attempt_receipt_check_14" CHECK ((binding_receipt_id IS NOT NULL) = (event_kind IN ('review_observed','incident'))), + CONSTRAINT "job_attempt_receipt_check_15" CHECK ( + (event_kind = 'incident' AND phase IS NOT NULL AND category IS NOT NULL AND recovered IS NOT NULL + AND recovered = (recovery IS NOT NULL)) + OR (event_kind <> 'incident' AND phase IS NULL AND category IS NULL AND recovered IS NULL AND recovery IS NULL) + ) +); +--> statement-breakpoint +ALTER TABLE "jobs" ADD COLUMN "active_claim_receipt_id" bigint;--> statement-breakpoint +ALTER TABLE "jobs" ADD COLUMN "staged_review_id" bigint;--> statement-breakpoint +ALTER TABLE "reviews" ADD COLUMN "origin_claim_receipt_id" bigint;--> statement-breakpoint +ALTER TABLE "job_attempt_receipts" ADD CONSTRAINT "job_attempt_receipts_predecessor_id_job_attempt_receipts_receipt_id_fk" FOREIGN KEY ("predecessor_id") REFERENCES "public"."job_attempt_receipts"("receipt_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "job_attempt_receipts" ADD CONSTRAINT "job_attempt_receipts_claim_receipt_id_job_id_ordinal_job_kind_claim_kind_job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_fk" FOREIGN KEY ("claim_receipt_id","job_id","ordinal","job_kind","claim_kind") REFERENCES "public"."job_attempt_receipts"("receipt_id","job_id","ordinal","job_kind","event_kind") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "job_attempt_receipts" ADD CONSTRAINT "job_attempt_receipts_binding_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_binding_kind_job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_fk" FOREIGN KEY ("binding_receipt_id","claim_receipt_id","job_id","review_id","review_public_id","binding_kind") REFERENCES "public"."job_attempt_receipts"("receipt_id","claim_receipt_id","job_id","review_id","review_public_id","event_kind") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "job_attempt_one_closure" ON "job_attempt_receipts" USING btree ("claim_receipt_id") WHERE "job_attempt_receipts"."event_kind" = 'queue_closed';--> statement-breakpoint +CREATE UNIQUE INDEX "job_attempt_one_binding" ON "job_attempt_receipts" USING btree ("claim_receipt_id","review_id") WHERE "job_attempt_receipts"."event_kind" = 'review_bound';--> statement-breakpoint +CREATE UNIQUE INDEX "job_attempt_one_observation" ON "job_attempt_receipts" USING btree ("claim_receipt_id","review_id","outcome") WHERE "job_attempt_receipts"."event_kind" = 'review_observed';--> statement-breakpoint +ALTER TABLE "jobs" ADD CONSTRAINT "jobs_active_claim_receipt_id_job_attempt_receipts_receipt_id_fk" FOREIGN KEY ("active_claim_receipt_id") REFERENCES "public"."job_attempt_receipts"("receipt_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reviews" ADD CONSTRAINT "reviews_origin_claim_receipt_id_job_attempt_receipts_receipt_id_fk" FOREIGN KEY ("origin_claim_receipt_id") REFERENCES "public"."job_attempt_receipts"("receipt_id") ON DELETE no action ON UPDATE no action; +--> statement-breakpoint +INSERT INTO job_attempt_receipts(receipt_id,source_epoch,event_kind) VALUES (0,gen_random_uuid(),'genesis'); + +CREATE FUNCTION reject_receipt_mutation() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN RAISE EXCEPTION 'immutable receipt'; END $$; +CREATE TRIGGER job_attempt_receipts_immutable BEFORE UPDATE OR DELETE ON job_attempt_receipts +FOR EACH ROW EXECUTE FUNCTION reject_receipt_mutation(); +CREATE TRIGGER job_attempt_receipts_no_truncate BEFORE TRUNCATE ON job_attempt_receipts +FOR EACH STATEMENT EXECUTE FUNCTION reject_receipt_mutation(); + +CREATE FUNCTION validate_receipt_append() RETURNS trigger LANGUAGE plpgsql AS $$ +DECLARE current_head bigint; origin_job bigint; +BEGIN + IF NEW.event_kind = 'genesis' OR current_setting('transaction_isolation') <> 'read committed' + OR NOT EXISTS (SELECT 1 FROM pg_locks WHERE pid = pg_backend_pid() AND locktype = 'advisory' + AND classid = 17491 AND objid = 1 AND objsubid = 2 AND granted) + OR NOT EXISTS (SELECT 1 FROM pg_sequence WHERE seqrelid = 'job_attempt_receipt_ids'::regclass + AND seqcache = 1 AND NOT seqcycle AND seqincrement = 1) + THEN RAISE EXCEPTION 'invalid receipt append'; END IF; + SELECT receipt_id INTO current_head FROM job_attempt_receipts ORDER BY receipt_id DESC LIMIT 1; + IF NEW.predecessor_id IS DISTINCT FROM current_head THEN RAISE EXCEPTION 'invalid receipt predecessor'; END IF; + IF NEW.event_kind = 'claim' AND NOT EXISTS ( + SELECT 1 FROM jobs WHERE id = NEW.job_id AND kind = NEW.job_kind + AND status = 'running' AND attempts = NEW.ordinal AND active_claim_receipt_id IS NULL + ) THEN RAISE EXCEPTION 'invalid receipt claim'; END IF; + IF NEW.event_kind = 'review_bound' THEN + SELECT c.job_id INTO origin_job FROM reviews r JOIN job_attempt_receipts c + ON c.receipt_id = r.origin_claim_receipt_id AND c.event_kind = 'claim' + WHERE r.id = NEW.review_id AND r.public_id = NEW.review_public_id; + IF origin_job IS DISTINCT FROM NEW.job_id OR NOT EXISTS ( + SELECT 1 FROM reviews r JOIN jobs j ON j.id = NEW.job_id + WHERE r.id = NEW.review_id AND r.public_id = NEW.review_public_id + AND (r.origin_claim_receipt_id = NEW.claim_receipt_id OR j.staged_review_id = r.id) + ) THEN RAISE EXCEPTION 'invalid review binding'; END IF; + END IF; + RETURN NEW; +END $$; +CREATE TRIGGER job_attempt_receipts_append BEFORE INSERT ON job_attempt_receipts +FOR EACH ROW EXECUTE FUNCTION validate_receipt_append(); + +CREATE FUNCTION validate_job_receipt_pointers() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + IF NEW.active_claim_receipt_id IS NOT NULL AND NEW.active_claim_receipt_id IS DISTINCT FROM OLD.active_claim_receipt_id AND NOT EXISTS ( + SELECT 1 FROM job_attempt_receipts c WHERE c.receipt_id = NEW.active_claim_receipt_id + AND c.event_kind = 'claim' AND c.job_id = NEW.id AND c.job_kind = NEW.kind + AND c.ordinal = NEW.attempts AND NEW.status = 'running' + ) THEN RAISE EXCEPTION 'invalid active claim'; END IF; + IF NEW.staged_review_id IS NOT NULL AND NEW.staged_review_id IS DISTINCT FROM OLD.staged_review_id AND NOT EXISTS ( + SELECT 1 FROM reviews r JOIN job_attempt_receipts b ON b.review_id = r.id + AND b.review_public_id = r.public_id AND b.event_kind = 'review_bound' + WHERE r.id = NEW.staged_review_id AND b.job_id = NEW.id AND b.claim_receipt_id = NEW.active_claim_receipt_id + ) THEN RAISE EXCEPTION 'invalid staged review'; END IF; + RETURN NEW; +END $$; +CREATE TRIGGER jobs_receipt_pointers BEFORE UPDATE ON jobs FOR EACH ROW EXECUTE FUNCTION validate_job_receipt_pointers(); + +CREATE FUNCTION validate_review_origin() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + IF TG_OP = 'UPDATE' AND NEW.origin_claim_receipt_id IS DISTINCT FROM OLD.origin_claim_receipt_id THEN + RAISE EXCEPTION 'immutable review origin'; + END IF; + IF NEW.origin_claim_receipt_id IS NOT NULL AND NOT EXISTS ( + SELECT 1 FROM job_attempt_receipts c WHERE c.receipt_id = NEW.origin_claim_receipt_id AND c.event_kind = 'claim' + ) THEN RAISE EXCEPTION 'invalid review origin'; END IF; + RETURN NEW; +END $$; +CREATE TRIGGER reviews_receipt_origin BEFORE INSERT OR UPDATE ON reviews +FOR EACH ROW EXECUTE FUNCTION validate_review_origin(); +--> statement-breakpoint diff --git a/drizzle/meta/0064_snapshot.json b/drizzle/meta/0064_snapshot.json new file mode 100644 index 00000000..17ee3665 --- /dev/null +++ b/drizzle/meta/0064_snapshot.json @@ -0,0 +1,8783 @@ +{ + "id": "31fd9a18-d064-4279-ab36-bda354b5cfbb", + "prevId": "a4d88b6e-4d79-48ac-ba79-eb48c407f0b6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.billing_author_settlements": { + "name": "billing_author_settlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_starts_at": { + "name": "period_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "period_ends_at": { + "name": "period_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "active_author_count": { + "name": "active_author_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "unit_amount_cents": { + "name": "unit_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 600 + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_started_at": { + "name": "attempt_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_reconcile_at": { + "name": "next_reconcile_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_category": { + "name": "last_error_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_author_settlements_org_period_idx": { + "name": "billing_author_settlements_org_period_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_author_settlements_provider_transaction_idx": { + "name": "billing_author_settlements_provider_transaction_idx", + "columns": [ + { + "expression": "provider_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_author_settlements_status_reconcile_idx": { + "name": "billing_author_settlements_status_reconcile_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_reconcile_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_author_settlements_org_id_organizations_id_fk": { + "name": "billing_author_settlements_org_id_organizations_id_fk", + "tableFrom": "billing_author_settlements", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_author_settlements_period_check": { + "name": "billing_author_settlements_period_check", + "value": "\"billing_author_settlements\".\"period_starts_at\" < \"billing_author_settlements\".\"period_ends_at\"" + }, + "billing_author_settlements_author_count_check": { + "name": "billing_author_settlements_author_count_check", + "value": "\"billing_author_settlements\".\"active_author_count\" >= 0" + }, + "billing_author_settlements_amount_check": { + "name": "billing_author_settlements_amount_check", + "value": "\"billing_author_settlements\".\"unit_amount_cents\" = 600 AND \"billing_author_settlements\".\"total_amount_cents\" = \"billing_author_settlements\".\"active_author_count\" * \"billing_author_settlements\".\"unit_amount_cents\"" + }, + "billing_author_settlements_status_check": { + "name": "billing_author_settlements_status_check", + "value": "\"billing_author_settlements\".\"status\" IN ('pending', 'charging', 'reconciling', 'charged', 'no_charge', 'failed')" + }, + "billing_author_settlements_attempt_count_check": { + "name": "billing_author_settlements_attempt_count_check", + "value": "\"billing_author_settlements\".\"attempt_count\" >= 0" + }, + "billing_author_settlements_subscription_nonempty": { + "name": "billing_author_settlements_subscription_nonempty", + "value": "length(btrim(\"billing_author_settlements\".\"provider_subscription_id\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.billing_checkout_transactions": { + "name": "billing_checkout_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paddle'" + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_url": { + "name": "checkout_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'creating'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_error_category": { + "name": "last_error_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_checkout_transactions_provider_transaction_idx": { + "name": "billing_checkout_transactions_provider_transaction_idx", + "columns": [ + { + "expression": "provider_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_checkout_transactions_open_org_idx": { + "name": "billing_checkout_transactions_open_org_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"billing_checkout_transactions\".\"status\" IN ('creating', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_checkout_transactions_status_expiry_idx": { + "name": "billing_checkout_transactions_status_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_checkout_transactions_org_id_organizations_id_fk": { + "name": "billing_checkout_transactions_org_id_organizations_id_fk", + "tableFrom": "billing_checkout_transactions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_checkout_transactions_requested_by_user_id_users_id_fk": { + "name": "billing_checkout_transactions_requested_by_user_id_users_id_fk", + "tableFrom": "billing_checkout_transactions", + "tableTo": "users", + "columnsFrom": [ + "requested_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_checkout_transactions_provider_check": { + "name": "billing_checkout_transactions_provider_check", + "value": "\"billing_checkout_transactions\".\"provider\" = 'paddle'" + }, + "billing_checkout_transactions_status_check": { + "name": "billing_checkout_transactions_status_check", + "value": "\"billing_checkout_transactions\".\"status\" IN ('creating', 'pending', 'completed', 'failed', 'expired', 'canceled')" + }, + "billing_checkout_transactions_provider_transaction_nonempty": { + "name": "billing_checkout_transactions_provider_transaction_nonempty", + "value": "\"billing_checkout_transactions\".\"provider_transaction_id\" IS NULL OR length(btrim(\"billing_checkout_transactions\".\"provider_transaction_id\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.billing_credit_grants": { + "name": "billing_credit_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "billing_credit_grants_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin_script'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applies_at": { + "name": "applies_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_credit_grants_org_created_idx": { + "name": "billing_credit_grants_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_credit_grants_org_idempotency_idx": { + "name": "billing_credit_grants_org_idempotency_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_credit_grants_org_id_organizations_id_fk": { + "name": "billing_credit_grants_org_id_organizations_id_fk", + "tableFrom": "billing_credit_grants", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_credit_grants_amount_cents_positive": { + "name": "billing_credit_grants_amount_cents_positive", + "value": "\"billing_credit_grants\".\"amount_cents\" > 0" + }, + "billing_credit_grants_reason_nonempty": { + "name": "billing_credit_grants_reason_nonempty", + "value": "length(btrim(\"billing_credit_grants\".\"reason\")) > 0" + }, + "billing_credit_grants_actor_nonempty": { + "name": "billing_credit_grants_actor_nonempty", + "value": "length(btrim(\"billing_credit_grants\".\"actor\")) > 0" + }, + "billing_credit_grants_source_nonempty": { + "name": "billing_credit_grants_source_nonempty", + "value": "length(btrim(\"billing_credit_grants\".\"source\")) > 0" + }, + "billing_credit_grants_idempotency_key_nonempty": { + "name": "billing_credit_grants_idempotency_key_nonempty", + "value": "length(btrim(\"billing_credit_grants\".\"idempotency_key\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.billing_provider_events": { + "name": "billing_provider_events", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paddle'" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_object_id": { + "name": "provider_object_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_provider_events_org_occurred_idx": { + "name": "billing_provider_events_org_occurred_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_provider_events_type_occurred_idx": { + "name": "billing_provider_events_type_occurred_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_provider_events_org_id_organizations_id_fk": { + "name": "billing_provider_events_org_id_organizations_id_fk", + "tableFrom": "billing_provider_events", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_provider_events_provider_check": { + "name": "billing_provider_events_provider_check", + "value": "\"billing_provider_events\".\"provider\" = 'paddle'" + }, + "billing_provider_events_outcome_check": { + "name": "billing_provider_events_outcome_check", + "value": "\"billing_provider_events\".\"outcome\" IN ('processing', 'applied', 'stale', 'ignored', 'unmatched')" + }, + "billing_provider_events_event_type_nonempty": { + "name": "billing_provider_events_event_type_nonempty", + "value": "length(btrim(\"billing_provider_events\".\"event_type\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.billing_provider_subscriptions": { + "name": "billing_provider_subscriptions", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paddle'" + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_customer_id": { + "name": "provider_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_period_starts_at": { + "name": "current_period_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_ends_at": { + "name": "current_period_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "latest_event_occurred_at": { + "name": "latest_event_occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "latest_event_id": { + "name": "latest_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_provider_subscriptions_provider_id_idx": { + "name": "billing_provider_subscriptions_provider_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_provider_subscriptions_status_period_idx": { + "name": "billing_provider_subscriptions_status_period_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_period_ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_provider_subscriptions_org_id_organizations_id_fk": { + "name": "billing_provider_subscriptions_org_id_organizations_id_fk", + "tableFrom": "billing_provider_subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_provider_subscriptions_provider_check": { + "name": "billing_provider_subscriptions_provider_check", + "value": "\"billing_provider_subscriptions\".\"provider\" = 'paddle'" + }, + "billing_provider_subscriptions_status_check": { + "name": "billing_provider_subscriptions_status_check", + "value": "\"billing_provider_subscriptions\".\"status\" IN ('active', 'trialing', 'past_due', 'paused', 'canceled')" + }, + "billing_provider_subscriptions_provider_subscription_nonempty": { + "name": "billing_provider_subscriptions_provider_subscription_nonempty", + "value": "length(btrim(\"billing_provider_subscriptions\".\"provider_subscription_id\")) > 0" + }, + "billing_provider_subscriptions_provider_customer_nonempty": { + "name": "billing_provider_subscriptions_provider_customer_nonempty", + "value": "length(btrim(\"billing_provider_subscriptions\".\"provider_customer_id\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.cli_device_authorizations": { + "name": "cli_device_authorizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "cli_device_authorizations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "device_code_sha256": { + "name": "device_code_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "token_id": { + "name": "token_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "poll_count": { + "name": "poll_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "cli_device_authorizations_device_code_sha256_idx": { + "name": "cli_device_authorizations_device_code_sha256_idx", + "columns": [ + { + "expression": "device_code_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_device_authorizations_user_code_idx": { + "name": "cli_device_authorizations_user_code_idx", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_device_authorizations_user_id_users_id_fk": { + "name": "cli_device_authorizations_user_id_users_id_fk", + "tableFrom": "cli_device_authorizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_device_authorizations_org_id_organizations_id_fk": { + "name": "cli_device_authorizations_org_id_organizations_id_fk", + "tableFrom": "cli_device_authorizations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_device_authorizations_token_id_cli_tokens_id_fk": { + "name": "cli_device_authorizations_token_id_cli_tokens_id_fk", + "tableFrom": "cli_device_authorizations", + "tableTo": "cli_tokens", + "columnsFrom": [ + "token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cli_device_authorizations_status_check": { + "name": "cli_device_authorizations_status_check", + "value": "\"cli_device_authorizations\".\"status\" IN ('pending', 'approved', 'denied', 'claimed')" + } + }, + "isRLSEnabled": false + }, + "public.cli_refresh_sessions": { + "name": "cli_refresh_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "cli_refresh_sessions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cli_refresh_sessions_expiry_idx": { + "name": "cli_refresh_sessions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_refresh_sessions_user_id_users_id_fk": { + "name": "cli_refresh_sessions_user_id_users_id_fk", + "tableFrom": "cli_refresh_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cli_refresh_sessions_org_id_organizations_id_fk": { + "name": "cli_refresh_sessions_org_id_organizations_id_fk", + "tableFrom": "cli_refresh_sessions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cli_refresh_sessions_expiry_check": { + "name": "cli_refresh_sessions_expiry_check", + "value": "\"cli_refresh_sessions\".\"expires_at\" > \"cli_refresh_sessions\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.cli_refresh_tokens": { + "name": "cli_refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "cli_refresh_tokens_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "token_sha256": { + "name": "token_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cli_refresh_tokens_token_sha256_idx": { + "name": "cli_refresh_tokens_token_sha256_idx", + "columns": [ + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_refresh_tokens_current_session_idx": { + "name": "cli_refresh_tokens_current_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_refresh_tokens\".\"consumed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_refresh_tokens_session_idx": { + "name": "cli_refresh_tokens_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_refresh_tokens_session_id_cli_refresh_sessions_id_fk": { + "name": "cli_refresh_tokens_session_id_cli_refresh_sessions_id_fk", + "tableFrom": "cli_refresh_tokens", + "tableTo": "cli_refresh_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cli_refresh_tokens_expiry_check": { + "name": "cli_refresh_tokens_expiry_check", + "value": "\"cli_refresh_tokens\".\"expires_at\" > \"cli_refresh_tokens\".\"created_at\"" + }, + "cli_refresh_tokens_consumed_after_created_check": { + "name": "cli_refresh_tokens_consumed_after_created_check", + "value": "\"cli_refresh_tokens\".\"consumed_at\" IS NULL OR \"cli_refresh_tokens\".\"consumed_at\" >= \"cli_refresh_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.cli_tokens": { + "name": "cli_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "cli_tokens_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "token_sha256": { + "name": "token_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "refresh_session_id": { + "name": "refresh_session_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cli_tokens_token_sha256_idx": { + "name": "cli_tokens_token_sha256_idx", + "columns": [ + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_tokens_org_created_idx": { + "name": "cli_tokens_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_tokens_refresh_session_idx": { + "name": "cli_tokens_refresh_session_idx", + "columns": [ + { + "expression": "refresh_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_tokens_user_id_users_id_fk": { + "name": "cli_tokens_user_id_users_id_fk", + "tableFrom": "cli_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cli_tokens_org_id_organizations_id_fk": { + "name": "cli_tokens_org_id_organizations_id_fk", + "tableFrom": "cli_tokens", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cli_tokens_refresh_session_id_cli_refresh_sessions_id_fk": { + "name": "cli_tokens_refresh_session_id_cli_refresh_sessions_id_fk", + "tableFrom": "cli_tokens", + "tableTo": "cli_refresh_sessions", + "columnsFrom": [ + "refresh_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cli_tokens_scope_check": { + "name": "cli_tokens_scope_check", + "value": "\"cli_tokens\".\"scope\" IN ('inference')" + } + }, + "isRLSEnabled": false + }, + "public.customer_notification_email_deliveries": { + "name": "customer_notification_email_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "email_category": { + "name": "email_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_count": { + "name": "event_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "customer_notification_email_deliveries_status_created_idx": { + "name": "customer_notification_email_deliveries_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "customer_notification_email_deliveries_org_created_idx": { + "name": "customer_notification_email_deliveries_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_notification_email_deliveries_org_id_organizations_id_fk": { + "name": "customer_notification_email_deliveries_org_id_organizations_id_fk", + "tableFrom": "customer_notification_email_deliveries", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "customer_notification_email_deliveries_category_check": { + "name": "customer_notification_email_deliveries_category_check", + "value": "\"customer_notification_email_deliveries\".\"email_category\" IN ('security', 'payment_failure', 'trial_expiry', 'service_incident', 'billing_summary')" + }, + "customer_notification_email_deliveries_status_check": { + "name": "customer_notification_email_deliveries_status_check", + "value": "\"customer_notification_email_deliveries\".\"status\" IN ('queued', 'retrying', 'sending', 'delivered', 'suppressed', 'failed')" + }, + "customer_notification_email_deliveries_event_count_check": { + "name": "customer_notification_email_deliveries_event_count_check", + "value": "\"customer_notification_email_deliveries\".\"event_count\" BETWEEN 1 AND 20" + } + }, + "isRLSEnabled": false + }, + "public.customer_notification_email_delivery_events": { + "name": "customer_notification_email_delivery_events", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "customer_notification_email_delivery_events_delivery_idx": { + "name": "customer_notification_email_delivery_events_delivery_idx", + "columns": [ + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_notification_email_delivery_events_delivery_id_customer_notification_email_deliveries_id_fk": { + "name": "customer_notification_email_delivery_events_delivery_id_customer_notification_email_deliveries_id_fk", + "tableFrom": "customer_notification_email_delivery_events", + "tableTo": "customer_notification_email_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_notification_events": { + "name": "customer_notification_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "customer_notification_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_label": { + "name": "action_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_href": { + "name": "action_href", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "customer_notification_events_org_key_idx": { + "name": "customer_notification_events_org_key_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "customer_notification_events_org_created_idx": { + "name": "customer_notification_events_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "customer_notification_events_expiry_idx": { + "name": "customer_notification_events_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_notification_events_org_id_organizations_id_fk": { + "name": "customer_notification_events_org_id_organizations_id_fk", + "tableFrom": "customer_notification_events", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "customer_notification_events_severity_check": { + "name": "customer_notification_events_severity_check", + "value": "\"customer_notification_events\".\"severity\" IN ('info', 'warning', 'critical')" + }, + "customer_notification_events_category_check": { + "name": "customer_notification_events_category_check", + "value": "\"customer_notification_events\".\"category\" IN ('trial', 'billing', 'service', 'security')" + }, + "customer_notification_events_visibility_check": { + "name": "customer_notification_events_visibility_check", + "value": "\"customer_notification_events\".\"visibility\" IN ('members', 'admins')" + }, + "customer_notification_events_content_check": { + "name": "customer_notification_events_content_check", + "value": "length(btrim(\"customer_notification_events\".\"idempotency_key\")) BETWEEN 1 AND 200 AND length(btrim(\"customer_notification_events\".\"title\")) BETWEEN 1 AND 120 AND length(btrim(\"customer_notification_events\".\"body\")) BETWEEN 1 AND 500" + }, + "customer_notification_events_action_check": { + "name": "customer_notification_events_action_check", + "value": "(\"customer_notification_events\".\"action_label\" IS NULL AND \"customer_notification_events\".\"action_href\" IS NULL) OR (\"customer_notification_events\".\"action_label\" IS NOT NULL AND \"customer_notification_events\".\"action_href\" IS NOT NULL AND length(btrim(\"customer_notification_events\".\"action_label\")) BETWEEN 1 AND 60 AND \"customer_notification_events\".\"action_href\" ~ '^/orgs/')" + }, + "customer_notification_events_expiry_check": { + "name": "customer_notification_events_expiry_check", + "value": "\"customer_notification_events\".\"expires_at\" > \"customer_notification_events\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.customer_notification_reads": { + "name": "customer_notification_reads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "customer_notification_reads_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "event_id": { + "name": "event_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "customer_notification_reads_event_user_idx": { + "name": "customer_notification_reads_event_user_idx", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "customer_notification_reads_user_event_idx": { + "name": "customer_notification_reads_user_event_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_notification_reads_event_id_customer_notification_events_id_fk": { + "name": "customer_notification_reads_event_id_customer_notification_events_id_fk", + "tableFrom": "customer_notification_reads", + "tableTo": "customer_notification_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_notification_reads_user_id_users_id_fk": { + "name": "customer_notification_reads_user_id_users_id_fk", + "tableFrom": "customer_notification_reads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finding_approvals": { + "name": "finding_approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "finding_id": { + "name": "finding_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "actor_github_id": { + "name": "actor_github_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_login_snapshot": { + "name": "actor_login_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_role_snapshot": { + "name": "actor_role_snapshot", + "type": "finding_approval_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "verb": { + "name": "verb", + "type": "finding_approval_verb", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'approve'" + }, + "reason_tag": { + "name": "reason_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_self_dismissal": { + "name": "author_self_dismissal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "finding_kind": { + "name": "finding_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_severity": { + "name": "finding_severity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_confidence": { + "name": "finding_confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "finding_model": { + "name": "finding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_scorer_model": { + "name": "finding_scorer_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rationale": { + "name": "rationale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "finding_approval_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_org_id": { + "name": "source_org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_repository_id": { + "name": "source_repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_installation_id": { + "name": "source_github_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_repo_id": { + "name": "source_github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_pr_number": { + "name": "source_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source_head_sha": { + "name": "source_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_webhook_delivery_id": { + "name": "source_webhook_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_github_comment_id": { + "name": "source_github_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_comment_kind": { + "name": "source_comment_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_binding_state": { + "name": "source_binding_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "finding_approvals_active_idx": { + "name": "finding_approvals_active_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"finding_approvals\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_approvals_github_comment_idx": { + "name": "finding_approvals_github_comment_idx", + "columns": [ + { + "expression": "source_github_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_github_repo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_comment_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_github_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"finding_approvals\".\"source\" = 'github'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_approvals_github_delivery_idx": { + "name": "finding_approvals_github_delivery_idx", + "columns": [ + { + "expression": "source_webhook_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"finding_approvals\".\"source\" = 'github'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_approvals_review_idx": { + "name": "finding_approvals_review_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finding_approvals_review_id_reviews_id_fk": { + "name": "finding_approvals_review_id_reviews_id_fk", + "tableFrom": "finding_approvals", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "finding_approvals_actor_user_id_users_id_fk": { + "name": "finding_approvals_actor_user_id_users_id_fk", + "tableFrom": "finding_approvals", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "finding_approvals_revoked_by_user_id_users_id_fk": { + "name": "finding_approvals_revoked_by_user_id_users_id_fk", + "tableFrom": "finding_approvals", + "tableTo": "users", + "columnsFrom": [ + "revoked_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "finding_approvals_rationale_nonempty": { + "name": "finding_approvals_rationale_nonempty", + "value": "length(btrim(\"finding_approvals\".\"rationale\")) > 0" + }, + "finding_approvals_dismissal_check": { + "name": "finding_approvals_dismissal_check", + "value": "(\"finding_approvals\".\"verb\" = 'approve' AND \"finding_approvals\".\"reason_tag\" IS NULL AND \"finding_approvals\".\"author_self_dismissal\" = false AND \"finding_approvals\".\"finding_kind\" IS NULL AND \"finding_approvals\".\"finding_severity\" IS NULL AND \"finding_approvals\".\"finding_confidence\" IS NULL AND \"finding_approvals\".\"finding_model\" IS NULL AND \"finding_approvals\".\"finding_scorer_model\" IS NULL) OR (\"finding_approvals\".\"verb\" = 'dismiss' AND \"finding_approvals\".\"reason_tag\" IS NOT NULL AND \"finding_approvals\".\"reason_tag\" IN ('false-positive', 'accepted-risk', 'out-of-scope') AND \"finding_approvals\".\"finding_kind\" IS NOT NULL AND \"finding_approvals\".\"finding_severity\" IS NOT NULL AND \"finding_approvals\".\"finding_confidence\" IS NOT NULL AND \"finding_approvals\".\"finding_confidence\" BETWEEN 0 AND 1 AND \"finding_approvals\".\"finding_model\" IS NOT NULL)" + }, + "finding_approvals_binding_check": { + "name": "finding_approvals_binding_check", + "value": "(\"finding_approvals\".\"source_binding_state\" = 'legacy' AND \"finding_approvals\".\"source_org_id\" IS NULL AND \"finding_approvals\".\"source_repository_id\" IS NULL AND \"finding_approvals\".\"source_github_installation_id\" IS NULL AND \"finding_approvals\".\"source_github_repo_id\" IS NULL AND \"finding_approvals\".\"source_pr_number\" IS NULL AND \"finding_approvals\".\"source_head_sha\" IS NULL) OR (\"finding_approvals\".\"source_binding_state\" = 'exact' AND \"finding_approvals\".\"source_org_id\" > 0 AND \"finding_approvals\".\"source_repository_id\" > 0 AND \"finding_approvals\".\"source_github_installation_id\" > 0 AND \"finding_approvals\".\"source_github_repo_id\" > 0 AND \"finding_approvals\".\"source_pr_number\" > 0 AND length(btrim(\"finding_approvals\".\"source_head_sha\")) BETWEEN 1 AND 200)" + }, + "finding_approvals_github_source_check": { + "name": "finding_approvals_github_source_check", + "value": "\"finding_approvals\".\"source\" <> 'github' OR (\"finding_approvals\".\"source_webhook_delivery_id\" IS NULL AND \"finding_approvals\".\"source_github_comment_id\" IS NULL AND \"finding_approvals\".\"source_comment_kind\" IS NULL) OR (length(btrim(\"finding_approvals\".\"source_webhook_delivery_id\")) BETWEEN 1 AND 200 AND \"finding_approvals\".\"source_github_comment_id\" > 0 AND \"finding_approvals\".\"source_comment_kind\" IN ('issue_comment', 'pull_request_review_comment'))" + } + }, + "isRLSEnabled": false + }, + "public.finding_feedback": { + "name": "finding_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "finding_feedback_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "finding_publication_id": { + "name": "finding_publication_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_github_comment_id": { + "name": "source_github_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_reaction_id": { + "name": "source_github_reaction_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "reaction_content": { + "name": "reaction_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_github_id": { + "name": "actor_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "actor_login_snapshot": { + "name": "actor_login_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author_github_id": { + "name": "pr_author_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "pr_author_login_snapshot": { + "name": "pr_author_login_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_is_pr_author": { + "name": "actor_is_pr_author", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "source_delivery_id": { + "name": "source_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "suggested_reason_tag": { + "name": "suggested_reason_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finding_feedback_publication_observed_idx": { + "name": "finding_feedback_publication_observed_idx", + "columns": [ + { + "expression": "finding_publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_feedback_github_reply_idx": { + "name": "finding_feedback_github_reply_idx", + "columns": [ + { + "expression": "source_github_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"finding_feedback\".\"source\" = 'reply'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_feedback_github_reaction_idx": { + "name": "finding_feedback_github_reaction_idx", + "columns": [ + { + "expression": "source_github_reaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"finding_feedback\".\"source\" = 'reaction'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finding_feedback_finding_publication_id_finding_publications_id_fk": { + "name": "finding_feedback_finding_publication_id_finding_publications_id_fk", + "tableFrom": "finding_feedback", + "tableTo": "finding_publications", + "columnsFrom": [ + "finding_publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "finding_feedback_source_check": { + "name": "finding_feedback_source_check", + "value": "\"finding_feedback\".\"source\" IN ('reply', 'reaction')" + }, + "finding_feedback_identity_check": { + "name": "finding_feedback_identity_check", + "value": "(\"finding_feedback\".\"source\" = 'reply' AND \"finding_feedback\".\"source_github_comment_id\" IS NOT NULL AND \"finding_feedback\".\"source_github_comment_id\" BETWEEN 1 AND 9007199254740991 AND \"finding_feedback\".\"source_github_reaction_id\" IS NULL AND \"finding_feedback\".\"reaction_content\" IS NULL AND \"finding_feedback\".\"body\" IS NOT NULL AND length(btrim(\"finding_feedback\".\"body\")) BETWEEN 1 AND 65535 AND length(btrim(\"finding_feedback\".\"source_delivery_id\")) BETWEEN 1 AND 200) OR (\"finding_feedback\".\"source\" = 'reaction' AND \"finding_feedback\".\"source_github_comment_id\" IS NOT NULL AND \"finding_feedback\".\"source_github_comment_id\" BETWEEN 1 AND 9007199254740991 AND \"finding_feedback\".\"source_github_reaction_id\" IS NOT NULL AND \"finding_feedback\".\"source_github_reaction_id\" BETWEEN 1 AND 9007199254740991 AND \"finding_feedback\".\"reaction_content\" IS NOT NULL AND \"finding_feedback\".\"reaction_content\" IN ('+1', '-1', 'unknown') AND \"finding_feedback\".\"body\" IS NULL AND \"finding_feedback\".\"source_delivery_id\" IS NULL)" + }, + "finding_feedback_actor_check": { + "name": "finding_feedback_actor_check", + "value": "\"finding_feedback\".\"actor_github_id\" BETWEEN 1 AND 9007199254740991 AND length(btrim(\"finding_feedback\".\"actor_login_snapshot\")) BETWEEN 1 AND 100 AND \"finding_feedback\".\"pr_author_github_id\" BETWEEN 1 AND 9007199254740991 AND length(btrim(\"finding_feedback\".\"pr_author_login_snapshot\")) BETWEEN 1 AND 100 AND \"finding_feedback\".\"actor_is_pr_author\" = (\"finding_feedback\".\"actor_github_id\" = \"finding_feedback\".\"pr_author_github_id\")" + }, + "finding_feedback_suggested_reason_check": { + "name": "finding_feedback_suggested_reason_check", + "value": "\"finding_feedback\".\"suggested_reason_tag\" IS NULL OR \"finding_feedback\".\"suggested_reason_tag\" IN ('false-positive', 'accepted-risk', 'out-of-scope')" + } + }, + "isRLSEnabled": false + }, + "public.finding_feedback_reconciliations": { + "name": "finding_feedback_reconciliations", + "schema": "", + "columns": { + "finding_publication_id": { + "name": "finding_publication_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_reconcile_at": { + "name": "next_reconcile_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_successful_at": { + "name": "last_successful_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finding_feedback_reconcile_due_idx": { + "name": "finding_feedback_reconcile_due_idx", + "columns": [ + { + "expression": "next_reconcile_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finding_feedback_reconciliations_finding_publication_id_finding_publications_id_fk": { + "name": "finding_feedback_reconciliations_finding_publication_id_finding_publications_id_fk", + "tableFrom": "finding_feedback_reconciliations", + "tableTo": "finding_publications", + "columnsFrom": [ + "finding_publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "finding_feedback_reconcile_attempt_count_check": { + "name": "finding_feedback_reconcile_attempt_count_check", + "value": "\"finding_feedback_reconciliations\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.finding_publications": { + "name": "finding_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "finding_publications_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "finding_id": { + "name": "finding_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stable_identity": { + "name": "stable_identity", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "initial_state": { + "name": "initial_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_state": { + "name": "current_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_comment_id": { + "name": "github_comment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_observed_at": { + "name": "lifecycle_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finding_publications_review_finding_idx": { + "name": "finding_publications_review_finding_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_publications_comment_idx": { + "name": "finding_publications_comment_idx", + "columns": [ + { + "expression": "github_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finding_publications_stable_finding_idx": { + "name": "finding_publications_stable_finding_idx", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stable_identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finding_publications_review_id_reviews_id_fk": { + "name": "finding_publications_review_id_reviews_id_fk", + "tableFrom": "finding_publications", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "finding_publications_finding_id_check": { + "name": "finding_publications_finding_id_check", + "value": "length(btrim(\"finding_publications\".\"finding_id\")) BETWEEN 1 AND 500" + }, + "finding_publications_initial_state_check": { + "name": "finding_publications_initial_state_check", + "value": "\"finding_publications\".\"initial_state\" IN ('inline', 'fileComment', 'checkAnnotation', 'summaryOnly', 'carried', 'resolved', 'suppressed', 'inlineRejected', 'unknown')" + }, + "finding_publications_current_state_check": { + "name": "finding_publications_current_state_check", + "value": "\"finding_publications\".\"current_state\" IN ('inline', 'fileComment', 'checkAnnotation', 'summaryOnly', 'carried', 'resolved', 'suppressed', 'inlineRejected', 'outdated', 'deleted', 'unknown')" + }, + "finding_publications_github_comment_id_check": { + "name": "finding_publications_github_comment_id_check", + "value": "\"finding_publications\".\"github_comment_id\" IS NULL OR \"finding_publications\".\"github_comment_id\" ~ '^[1-9][0-9]{0,19}$'" + }, + "finding_publications_file_comment_identity_check": { + "name": "finding_publications_file_comment_identity_check", + "value": "(\"finding_publications\".\"initial_state\" <> 'fileComment' AND \"finding_publications\".\"current_state\" <> 'fileComment') OR \"finding_publications\".\"github_comment_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.github_webhook_delivery_recoveries": { + "name": "github_webhook_delivery_recoveries", + "schema": "", + "columns": { + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "delivery_guid": { + "name": "delivery_guid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redelivery": { + "name": "redelivery", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "request_state": { + "name": "request_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_attempts": { + "name": "request_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_requested_at": { + "name": "last_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "request_status_code": { + "name": "request_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recovery_delivery_id": { + "name": "recovery_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_category": { + "name": "last_error_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_webhook_delivery_recoveries_guid_idx": { + "name": "github_webhook_delivery_recoveries_guid_idx", + "columns": [ + { + "expression": "delivery_guid", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_webhook_delivery_recoveries_retry_idx": { + "name": "github_webhook_delivery_recoveries_retry_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"github_webhook_delivery_recoveries\".\"outcome\" = 'failure' AND \"github_webhook_delivery_recoveries\".\"recovery_delivery_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_webhook_delivery_recoveries_outcome_check": { + "name": "github_webhook_delivery_recoveries_outcome_check", + "value": "\"github_webhook_delivery_recoveries\".\"outcome\" IN ('success', 'failure', 'pending')" + }, + "github_webhook_delivery_recoveries_request_state_check": { + "name": "github_webhook_delivery_recoveries_request_state_check", + "value": "\"github_webhook_delivery_recoveries\".\"request_state\" IS NULL OR \"github_webhook_delivery_recoveries\".\"request_state\" IN ('requesting', 'retryable', 'accepted', 'terminal', 'exhausted', 'recovered')" + }, + "github_webhook_delivery_recoveries_attempts_check": { + "name": "github_webhook_delivery_recoveries_attempts_check", + "value": "\"github_webhook_delivery_recoveries\".\"request_attempts\" >= 0 AND \"github_webhook_delivery_recoveries\".\"request_attempts\" <= 2" + } + }, + "isRLSEnabled": false + }, + "public.github_webhook_redelivery_state": { + "name": "github_webhook_redelivery_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sweep_started_at": { + "name": "sweep_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_page_at": { + "name": "last_page_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sweep_completed_at": { + "name": "last_sweep_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rate_limited_until": { + "name": "rate_limited_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_category": { + "name": "last_error_category", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_webhook_redelivery_state_singleton_check": { + "name": "github_webhook_redelivery_state_singleton_check", + "value": "\"github_webhook_redelivery_state\".\"id\" = 1" + } + }, + "isRLSEnabled": false + }, + "public.hosted_provider_keys": { + "name": "hosted_provider_keys", + "schema": "", + "columns": { + "create_intent_id": { + "name": "create_intent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_key_name": { + "name": "provider_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_key_hash": { + "name": "provider_key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conflicting_provider_key_hash": { + "name": "conflicting_provider_key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sealed_runtime_key": { + "name": "sealed_runtime_key", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entitlement_period_starts_at": { + "name": "entitlement_period_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "entitlement_period_ends_at": { + "name": "entitlement_period_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "entitlement_updated_at": { + "name": "entitlement_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit_micros": { + "name": "limit_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "create_attempted_at": { + "name": "create_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "create_outcome": { + "name": "create_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revocation_requested_at": { + "name": "revocation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoke_attempted_at": { + "name": "revoke_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoke_outcome": { + "name": "revoke_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reconciliation_required_at": { + "name": "reconciliation_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_kind": { + "name": "lease_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "clock_timestamp()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "clock_timestamp()" + } + }, + "indexes": { + "hosted_provider_keys_provider_key_hash_unique": { + "name": "hosted_provider_keys_provider_key_hash_unique", + "columns": [ + { + "expression": "provider_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"hosted_provider_keys\".\"provider_key_hash\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "hosted_provider_keys_entitlement_binding_unique": { + "name": "hosted_provider_keys_entitlement_binding_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entitlement_period_starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entitlement_period_ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "limit_micros", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"hosted_provider_keys\".\"state\" NOT IN ('revoked', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "hosted_provider_keys_active_org_unique": { + "name": "hosted_provider_keys_active_org_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"hosted_provider_keys\".\"state\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "hosted_provider_keys_runtime_org_unique": { + "name": "hosted_provider_keys_runtime_org_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"hosted_provider_keys\".\"sealed_runtime_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "hosted_provider_keys_reconciliation_idx": { + "name": "hosted_provider_keys_reconciliation_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reconciliation_required_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hosted_provider_keys_org_id_organizations_id_fk": { + "name": "hosted_provider_keys_org_id_organizations_id_fk", + "tableFrom": "hosted_provider_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hosted_provider_keys_provider_key_name_unique": { + "name": "hosted_provider_keys_provider_key_name_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_key_name" + ] + } + }, + "policies": {}, + "checkConstraints": { + "hosted_provider_keys_state_check": { + "name": "hosted_provider_keys_state_check", + "value": "\"hosted_provider_keys\".\"state\" IN ('provisioning', 'activating', 'active', 'rejected', 'orphaned', 'revocation_pending', 'revoked', 'cancelled')" + }, + "hosted_provider_keys_provider_key_name_nonempty": { + "name": "hosted_provider_keys_provider_key_name_nonempty", + "value": "length(btrim(\"hosted_provider_keys\".\"provider_key_name\")) > 0" + }, + "hosted_provider_keys_provider_key_hash_nonempty": { + "name": "hosted_provider_keys_provider_key_hash_nonempty", + "value": "\"hosted_provider_keys\".\"provider_key_hash\" IS NULL OR length(btrim(\"hosted_provider_keys\".\"provider_key_hash\")) > 0" + }, + "hosted_provider_keys_conflicting_hash_nonempty": { + "name": "hosted_provider_keys_conflicting_hash_nonempty", + "value": "\"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL OR length(btrim(\"hosted_provider_keys\".\"conflicting_provider_key_hash\")) > 0" + }, + "hosted_provider_keys_entitlement_period_check": { + "name": "hosted_provider_keys_entitlement_period_check", + "value": "\"hosted_provider_keys\".\"entitlement_period_ends_at\" > \"hosted_provider_keys\".\"entitlement_period_starts_at\"" + }, + "hosted_provider_keys_limit_exact_range": { + "name": "hosted_provider_keys_limit_exact_range", + "value": "\"hosted_provider_keys\".\"limit_micros\" > 0 AND \"hosted_provider_keys\".\"limit_micros\" <= 2251799813685247" + }, + "hosted_provider_keys_create_outcome_check": { + "name": "hosted_provider_keys_create_outcome_check", + "value": "\"hosted_provider_keys\".\"create_outcome\" IS NULL OR \"hosted_provider_keys\".\"create_outcome\" IN ('created', 'rejected', 'rate_limited', 'ambiguous', 'name_present', 'name_not_unique', 'credential_persistence_failed', 'intent_changed', 'ownership_conflict')" + }, + "hosted_provider_keys_revoke_outcome_check": { + "name": "hosted_provider_keys_revoke_outcome_check", + "value": "\"hosted_provider_keys\".\"revoke_outcome\" IS NULL OR \"hosted_provider_keys\".\"revoke_outcome\" IN ('ambiguous', 'rejected', 'disabled', 'absent')" + }, + "hosted_provider_keys_lease_shape": { + "name": "hosted_provider_keys_lease_shape", + "value": "(\n \"hosted_provider_keys\".\"lease_id\" IS NULL\n AND \"hosted_provider_keys\".\"lease_kind\" IS NULL\n AND \"hosted_provider_keys\".\"lease_expires_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"lease_id\" IS NOT NULL\n AND \"hosted_provider_keys\".\"lease_kind\" IN ('create', 'revoke')\n AND \"hosted_provider_keys\".\"lease_expires_at\" IS NOT NULL\n )" + }, + "hosted_provider_keys_lease_state": { + "name": "hosted_provider_keys_lease_state", + "value": "\"hosted_provider_keys\".\"lease_id\" IS NULL OR (\n (\"hosted_provider_keys\".\"lease_kind\" = 'create' AND \"hosted_provider_keys\".\"state\" IN ('provisioning', 'activating', 'orphaned'))\n OR (\"hosted_provider_keys\".\"lease_kind\" = 'revoke' AND \"hosted_provider_keys\".\"state\" = 'revocation_pending')\n )" + }, + "hosted_provider_keys_lifecycle_shape": { + "name": "hosted_provider_keys_lifecycle_shape", + "value": "(\n \"hosted_provider_keys\".\"state\" = 'provisioning'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'activating'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NOT NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"create_outcome\" = 'created'\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'active'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NOT NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NOT NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"create_outcome\" = 'created'\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NULL\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'rejected'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"create_outcome\" = 'rejected'\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NULL\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'orphaned'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"create_outcome\" IN ('ambiguous', 'name_present', 'name_not_unique', 'credential_persistence_failed', 'intent_changed', 'ownership_conflict')\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n AND (\n (\"hosted_provider_keys\".\"create_outcome\" = 'ownership_conflict' AND \"hosted_provider_keys\".\"provider_key_hash\" IS NULL AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NOT NULL)\n OR (\"hosted_provider_keys\".\"create_outcome\" <> 'ownership_conflict' AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL)\n )\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'revocation_pending'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NOT NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"create_outcome\" IN ('created', 'ambiguous', 'credential_persistence_failed')\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'revoked'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NOT NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"create_outcome\" IN ('created', 'ambiguous', 'credential_persistence_failed')\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IN ('disabled', 'absent')\n AND \"hosted_provider_keys\".\"revoked_at\" IS NOT NULL\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NULL\n AND \"hosted_provider_keys\".\"lease_id\" IS NULL\n ) OR (\n \"hosted_provider_keys\".\"state\" = 'cancelled'\n AND \"hosted_provider_keys\".\"sealed_runtime_key\" IS NULL\n AND \"hosted_provider_keys\".\"provider_key_hash\" IS NULL\n AND \"hosted_provider_keys\".\"conflicting_provider_key_hash\" IS NULL\n AND (\n (\"hosted_provider_keys\".\"create_attempted_at\" IS NULL AND \"hosted_provider_keys\".\"create_outcome\" IS NULL)\n OR (\"hosted_provider_keys\".\"create_attempted_at\" IS NOT NULL AND \"hosted_provider_keys\".\"create_outcome\" = 'rate_limited')\n )\n AND \"hosted_provider_keys\".\"revocation_requested_at\" IS NULL\n AND \"hosted_provider_keys\".\"revoke_outcome\" IS NULL\n AND \"hosted_provider_keys\".\"revoked_at\" IS NULL\n AND \"hosted_provider_keys\".\"reconciliation_required_at\" IS NULL\n AND \"hosted_provider_keys\".\"lease_id\" IS NULL\n )" + } + }, + "isRLSEnabled": false + }, + "public.hosted_usage_reservations": { + "name": "hosted_usage_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'review'" + }, + "reserved_micros": { + "name": "reserved_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "actual_micros": { + "name": "actual_micros", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "hosted_usage_reservations_review_idx": { + "name": "hosted_usage_reservations_review_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hosted_usage_reservations_active_org_expiry_idx": { + "name": "hosted_usage_reservations_active_org_expiry_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"hosted_usage_reservations\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hosted_usage_reservations_org_id_organizations_id_fk": { + "name": "hosted_usage_reservations_org_id_organizations_id_fk", + "tableFrom": "hosted_usage_reservations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hosted_usage_reservations_review_id_reviews_id_fk": { + "name": "hosted_usage_reservations_review_id_reviews_id_fk", + "tableFrom": "hosted_usage_reservations", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "hosted_usage_reservations_status_check": { + "name": "hosted_usage_reservations_status_check", + "value": "\"hosted_usage_reservations\".\"status\" IN ('active', 'reconciled', 'released')" + }, + "hosted_usage_reservations_operation_check": { + "name": "hosted_usage_reservations_operation_check", + "value": "\"hosted_usage_reservations\".\"operation\" IN ('review', 'respond', 'cli_gateway')" + }, + "hosted_usage_reservations_operation_reference_check": { + "name": "hosted_usage_reservations_operation_reference_check", + "value": "(\"hosted_usage_reservations\".\"operation\" = 'review' AND \"hosted_usage_reservations\".\"review_id\" IS NOT NULL) OR (\"hosted_usage_reservations\".\"operation\" IN ('respond', 'cli_gateway') AND \"hosted_usage_reservations\".\"review_id\" IS NULL)" + }, + "hosted_usage_reservations_reserved_positive": { + "name": "hosted_usage_reservations_reserved_positive", + "value": "\"hosted_usage_reservations\".\"reserved_micros\" > 0" + }, + "hosted_usage_reservations_actual_nonnegative": { + "name": "hosted_usage_reservations_actual_nonnegative", + "value": "\"hosted_usage_reservations\".\"actual_micros\" IS NULL OR \"hosted_usage_reservations\".\"actual_micros\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.ilert_alert_events": { + "name": "ilert_alert_events", + "schema": "", + "columns": { + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "ilert_alert_events_sequence_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alert_source_id": { + "name": "alert_source_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "alert_source_name": { + "name": "alert_source_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report_time": { + "name": "report_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ilert_alert_events_event_id_idx": { + "name": "ilert_alert_events_event_id_idx", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ilert_alert_events_alert_sequence_idx": { + "name": "ilert_alert_events_alert_sequence_idx", + "columns": [ + { + "expression": "alert_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ilert_alert_events_alert_id_check": { + "name": "ilert_alert_events_alert_id_check", + "value": "\"ilert_alert_events\".\"alert_id\" ~ '^[1-9][0-9]{0,63}$'" + }, + "ilert_alert_events_event_type_check": { + "name": "ilert_alert_events_event_type_check", + "value": "length(\"ilert_alert_events\".\"event_type\") <= 64 AND \"ilert_alert_events\".\"event_type\" ~ '^alert-[a-z]+(-[a-z]+)*$'" + }, + "ilert_alert_events_status_check": { + "name": "ilert_alert_events_status_check", + "value": "\"ilert_alert_events\".\"status\" IN ('PENDING', 'ACCEPTED', 'RESOLVED')" + }, + "ilert_alert_events_priority_check": { + "name": "ilert_alert_events_priority_check", + "value": "\"ilert_alert_events\".\"priority\" IN ('HIGH', 'LOW')" + }, + "ilert_alert_events_summary_check": { + "name": "ilert_alert_events_summary_check", + "value": "length(\"ilert_alert_events\".\"summary\") BETWEEN 1 AND 512" + }, + "ilert_alert_events_details_check": { + "name": "ilert_alert_events_details_check", + "value": "length(\"ilert_alert_events\".\"details\") BETWEEN 0 AND 8192" + }, + "ilert_alert_events_source_name_check": { + "name": "ilert_alert_events_source_name_check", + "value": "length(\"ilert_alert_events\".\"alert_source_name\") BETWEEN 1 AND 256" + }, + "ilert_alert_events_payload_sha256_check": { + "name": "ilert_alert_events_payload_sha256_check", + "value": "\"ilert_alert_events\".\"payload_sha256\" ~ '^[0-9a-f]{64}$'" + } + }, + "isRLSEnabled": false + }, + "public.installations": { + "name": "installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "installations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "github_installation_id": { + "name": "github_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suspended": { + "name": "suspended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "installations_org_idx": { + "name": "installations_org_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "installations_org_id_organizations_id_fk": { + "name": "installations_org_id_organizations_id_fk", + "tableFrom": "installations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "installations_github_installation_id_unique": { + "name": "installations_github_installation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "github_installation_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jobs": { + "name": "jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "jobs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "job_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "run_after": { + "name": "run_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by": { + "name": "locked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "active_claim_receipt_id": { + "name": "active_claim_receipt_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "staged_review_id": { + "name": "staged_review_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "jobs_claim_idx": { + "name": "jobs_claim_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "jobs_running_locked_at_idx": { + "name": "jobs_running_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "jobs_running_org_concurrency_idx": { + "name": "jobs_running_org_concurrency_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\"->>'sourceOrgId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "jobs_active_claim_receipt_id_job_attempt_receipts_receipt_id_fk": { + "name": "jobs_active_claim_receipt_id_job_attempt_receipts_receipt_id_fk", + "tableFrom": "jobs", + "tableTo": "job_attempt_receipts", + "columnsFrom": [ + "active_claim_receipt_id" + ], + "columnsTo": [ + "receipt_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.large_review_attempts": { + "name": "large_review_attempts", + "schema": "", + "columns": { + "attempt_key": { + "name": "attempt_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "run_key": { + "name": "run_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_sha256": { + "name": "request_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_identity": { + "name": "batch_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_headers": { + "name": "response_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "large_review_attempts_run_request_attempt_idx": { + "name": "large_review_attempts_run_request_attempt_idx", + "columns": [ + { + "expression": "run_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "large_review_attempts_pending_request_idx": { + "name": "large_review_attempts_pending_request_idx", + "columns": [ + { + "expression": "run_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"large_review_attempts\".\"state\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "large_review_attempts_run_idx": { + "name": "large_review_attempts_run_idx", + "columns": [ + { + "expression": "run_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "large_review_attempts_run_key_large_review_runs_run_key_fk": { + "name": "large_review_attempts_run_key_large_review_runs_run_key_fk", + "tableFrom": "large_review_attempts", + "tableTo": "large_review_runs", + "columnsFrom": [ + "run_key" + ], + "columnsTo": [ + "run_key" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "large_review_attempts_key_check": { + "name": "large_review_attempts_key_check", + "value": "\"large_review_attempts\".\"attempt_key\" ~ '^[0-9a-f]{64}$'" + }, + "large_review_attempts_request_check": { + "name": "large_review_attempts_request_check", + "value": "\"large_review_attempts\".\"request_sha256\" ~ '^[0-9a-f]{64}$' AND \"large_review_attempts\".\"batch_identity\" ~ '^[0-9a-f]{64}$'" + }, + "large_review_attempts_attempt_check": { + "name": "large_review_attempts_attempt_check", + "value": "\"large_review_attempts\".\"attempt\" BETWEEN 1 AND 10" + }, + "large_review_attempts_state_check": { + "name": "large_review_attempts_state_check", + "value": "\"large_review_attempts\".\"state\" IN ('pending', 'completed')" + }, + "large_review_attempts_response_check": { + "name": "large_review_attempts_response_check", + "value": "(\"large_review_attempts\".\"state\" = 'pending' AND \"large_review_attempts\".\"response_status\" IS NULL AND \"large_review_attempts\".\"response_headers\" IS NULL AND \"large_review_attempts\".\"response_body\" IS NULL AND \"large_review_attempts\".\"completed_at\" IS NULL) OR (\"large_review_attempts\".\"state\" = 'completed' AND \"large_review_attempts\".\"response_status\" BETWEEN 200 AND 299 AND \"large_review_attempts\".\"response_headers\" IS NOT NULL AND \"large_review_attempts\".\"response_body\" IS NOT NULL AND \"large_review_attempts\".\"completed_at\" IS NOT NULL)" + }, + "large_review_attempts_model_check": { + "name": "large_review_attempts_model_check", + "value": "length(btrim(\"large_review_attempts\".\"model\")) BETWEEN 1 AND 500" + } + }, + "isRLSEnabled": false + }, + "public.large_review_runs": { + "name": "large_review_runs", + "schema": "", + "columns": { + "run_key": { + "name": "run_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "current_review_id": { + "name": "current_review_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cli_version": { + "name": "cli_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration_sha256": { + "name": "configuration_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_identity": { + "name": "provider_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_sha": { + "name": "base_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "retry_lineage": { + "name": "retry_lineage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_sha256": { + "name": "plan_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hosted_reservation_id": { + "name": "hosted_reservation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_state": { + "name": "billing_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "conservatively_settled_at": { + "name": "conservatively_settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "large_review_runs_expiry_idx": { + "name": "large_review_runs_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "large_review_runs_resume_identity_idx": { + "name": "large_review_runs_resume_identity_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "head_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "base_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cli_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "configuration_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_lineage", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "large_review_runs_current_review_id_reviews_id_fk": { + "name": "large_review_runs_current_review_id_reviews_id_fk", + "tableFrom": "large_review_runs", + "tableTo": "reviews", + "columnsFrom": [ + "current_review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "large_review_runs_repository_id_repositories_id_fk": { + "name": "large_review_runs_repository_id_repositories_id_fk", + "tableFrom": "large_review_runs", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "large_review_runs_key_check": { + "name": "large_review_runs_key_check", + "value": "\"large_review_runs\".\"run_key\" ~ '^[0-9a-f]{64}$'" + }, + "large_review_runs_configuration_check": { + "name": "large_review_runs_configuration_check", + "value": "\"large_review_runs\".\"configuration_sha256\" ~ '^[0-9a-f]{64}$'" + }, + "large_review_runs_plan_check": { + "name": "large_review_runs_plan_check", + "value": "\"large_review_runs\".\"plan_sha256\" ~ '^[0-9a-f]{64}$'" + }, + "large_review_runs_identity_lengths_check": { + "name": "large_review_runs_identity_lengths_check", + "value": "\"large_review_runs\".\"pr_number\" > 0 AND length(btrim(\"large_review_runs\".\"cli_version\")) BETWEEN 1 AND 100 AND length(btrim(\"large_review_runs\".\"provider_identity\")) BETWEEN 1 AND 2048 AND length(btrim(\"large_review_runs\".\"head_sha\")) BETWEEN 1 AND 200 AND length(btrim(\"large_review_runs\".\"base_sha\")) BETWEEN 1 AND 200 AND length(btrim(\"large_review_runs\".\"retry_lineage\")) BETWEEN 1 AND 200" + }, + "large_review_runs_billing_state_check": { + "name": "large_review_runs_billing_state_check", + "value": "(\"large_review_runs\".\"billing_state\" = 'active' AND \"large_review_runs\".\"conservatively_settled_at\" IS NULL) OR (\"large_review_runs\".\"billing_state\" = 'conservative' AND \"large_review_runs\".\"conservatively_settled_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.operator_alert_deliveries": { + "name": "operator_alert_deliveries", + "schema": "", + "columns": { + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "github_installation_id": { + "name": "github_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "operator_alert_deliveries_status_created_idx": { + "name": "operator_alert_deliveries_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "operator_alert_deliveries_org_created_idx": { + "name": "operator_alert_deliveries_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "operator_alert_deliveries_org_id_organizations_id_fk": { + "name": "operator_alert_deliveries_org_id_organizations_id_fk", + "tableFrom": "operator_alert_deliveries", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "operator_alert_deliveries_event_check": { + "name": "operator_alert_deliveries_event_check", + "value": "\"operator_alert_deliveries\".\"event\" IN ('trial_started', 'trial_expired', 'installation_removed', 'subscription_started', 'subscription_past_due', 'subscription_paused', 'subscription_canceled', 'billing_anomaly', 'finding_feedback_digest')" + }, + "operator_alert_deliveries_status_check": { + "name": "operator_alert_deliveries_status_check", + "value": "\"operator_alert_deliveries\".\"status\" IN ('queued', 'retrying', 'delivered', 'failed')" + }, + "operator_alert_deliveries_event_key_nonempty": { + "name": "operator_alert_deliveries_event_key_nonempty", + "value": "length(btrim(\"operator_alert_deliveries\".\"event_key\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.org_config_probe_refreshes": { + "name": "org_config_probe_refreshes", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "org_config_probe_refreshes_org_id_organizations_id_fk": { + "name": "org_config_probe_refreshes_org_id_organizations_id_fk", + "tableFrom": "org_config_probe_refreshes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_config_snapshots": { + "name": "org_config_snapshots", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "source_repository_id": { + "name": "source_repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_repo_id": { + "name": "source_github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "source_full_name": { + "name": "source_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "commit_sha": { + "name": "commit_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_yaml": { + "name": "config_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "guardrails_md": { + "name": "guardrails_md", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_policy_md": { + "name": "content_policy_md", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "loaded_files": { + "name": "loaded_files", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "stale": { + "name": "stale", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "org_config_snapshots_org_id_organizations_id_fk": { + "name": "org_config_snapshots_org_id_organizations_id_fk", + "tableFrom": "org_config_snapshots", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_config_snapshots_source_repository_id_repositories_id_fk": { + "name": "org_config_snapshots_source_repository_id_repositories_id_fk", + "tableFrom": "org_config_snapshots", + "tableTo": "repositories", + "columnsFrom": [ + "source_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_members": { + "name": "org_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "org_members_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "org_members_org_user_idx": { + "name": "org_members_org_user_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_members_user_idx": { + "name": "org_members_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "org_members_org_id_organizations_id_fk": { + "name": "org_members_org_id_organizations_id_fk", + "tableFrom": "org_members", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_members_user_id_users_id_fk": { + "name": "org_members_user_id_users_id_fk", + "tableFrom": "org_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_settings": { + "name": "org_settings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "api_base": { + "name": "api_base", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_ciphertext": { + "name": "api_key_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "api_format": { + "name": "api_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'openai-compatible'" + }, + "api_auth_header_ciphertext": { + "name": "api_auth_header_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "api_auth_value_ciphertext": { + "name": "api_auth_value_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_cascade": { + "name": "model_cascade", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config_yaml": { + "name": "config_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "guardrails_md": { + "name": "guardrails_md", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_policy_md": { + "name": "content_policy_md", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_config_enabled": { + "name": "shared_config_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "gate_enabled": { + "name": "gate_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "escalation_email": { + "name": "escalation_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "escalation_email_pending": { + "name": "escalation_email_pending", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verified_at": { + "name": "escalation_email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_token_digest": { + "name": "escalation_email_verification_token_digest", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_token_ciphertext": { + "name": "escalation_email_verification_token_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_expires_at": { + "name": "escalation_email_verification_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_requested_at": { + "name": "escalation_email_verification_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_sent_at": { + "name": "escalation_email_verification_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "escalation_email_verification_message_id": { + "name": "escalation_email_verification_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "org_settings_org_id_organizations_id_fk": { + "name": "org_settings_org_id_organizations_id_fk", + "tableFrom": "org_settings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_entitlements": { + "name": "organization_entitlements", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "subscription_mode": { + "name": "subscription_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "past_due_grace_ends_at": { + "name": "past_due_grace_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "period_starts_at": { + "name": "period_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "period_ends_at": { + "name": "period_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "included_usage_micros": { + "name": "included_usage_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "overage_hard_cap_micros": { + "name": "overage_hard_cap_micros", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "0" + }, + "included_usage_cents": { + "name": "included_usage_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "overage_hard_cap_cents": { + "name": "overage_hard_cap_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "billing_contact_email": { + "name": "billing_contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verified_at": { + "name": "billing_contact_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "billing_contact_pending": { + "name": "billing_contact_pending", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_token_digest": { + "name": "billing_contact_verification_token_digest", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_token_ciphertext": { + "name": "billing_contact_verification_token_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_expires_at": { + "name": "billing_contact_verification_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_requested_at": { + "name": "billing_contact_verification_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_sent_at": { + "name": "billing_contact_verification_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "billing_contact_verification_message_id": { + "name": "billing_contact_verification_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promotional_eligible": { + "name": "promotional_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "promotional_ends_at": { + "name": "promotional_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_entitlements_org_id_organizations_id_fk": { + "name": "organization_entitlements_org_id_organizations_id_fk", + "tableFrom": "organization_entitlements", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_entitlements_subscription_mode_check": { + "name": "organization_entitlements_subscription_mode_check", + "value": "\"organization_entitlements\".\"subscription_mode\" IN ('hosted', 'byok')" + }, + "organization_entitlements_status_check": { + "name": "organization_entitlements_status_check", + "value": "\"organization_entitlements\".\"status\" IN ('active', 'trialing', 'past_due', 'suspended')" + }, + "organization_entitlements_included_usage_micros_nonnegative": { + "name": "organization_entitlements_included_usage_micros_nonnegative", + "value": "\"organization_entitlements\".\"included_usage_micros\" >= 0" + }, + "organization_entitlements_overage_cap_micros_nonnegative": { + "name": "organization_entitlements_overage_cap_micros_nonnegative", + "value": "\"organization_entitlements\".\"overage_hard_cap_micros\" IS NULL OR \"organization_entitlements\".\"overage_hard_cap_micros\" >= 0" + }, + "organization_entitlements_included_usage_nonnegative": { + "name": "organization_entitlements_included_usage_nonnegative", + "value": "\"organization_entitlements\".\"included_usage_cents\" >= 0" + }, + "organization_entitlements_overage_cap_nonnegative": { + "name": "organization_entitlements_overage_cap_nonnegative", + "value": "\"organization_entitlements\".\"overage_hard_cap_cents\" IS NULL OR \"organization_entitlements\".\"overage_hard_cap_cents\" >= 0" + }, + "organization_entitlements_updated_by_nonempty": { + "name": "organization_entitlements_updated_by_nonempty", + "value": "length(btrim(\"organization_entitlements\".\"updated_by\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.organization_notification_preferences": { + "name": "organization_notification_preferences", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "billing_summary_email": { + "name": "billing_summary_email", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "service_summary_email": { + "name": "service_summary_email", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_notification_preferences_org_id_organizations_id_fk": { + "name": "organization_notification_preferences_org_id_organizations_id_fk", + "tableFrom": "organization_notification_preferences", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_setting_events": { + "name": "organization_setting_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "organization_setting_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "setting": { + "name": "setting", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dashboard'" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_setting_events_org_time_idx": { + "name": "organization_setting_events_org_time_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_setting_events_org_id_organizations_id_fk": { + "name": "organization_setting_events_org_id_organizations_id_fk", + "tableFrom": "organization_setting_events", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_setting_events_actor_user_id_users_id_fk": { + "name": "organization_setting_events_actor_user_id_users_id_fk", + "tableFrom": "organization_setting_events", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_setting_events_setting_check": { + "name": "organization_setting_events_setting_check", + "value": "\"organization_setting_events\".\"setting\" IN ('gate_enabled', 'billing_summary_email', 'service_summary_email')" + }, + "organization_setting_events_value_check": { + "name": "organization_setting_events_value_check", + "value": "\"organization_setting_events\".\"value\" IN ('enabled', 'disabled', 'advisory')" + }, + "organization_setting_events_source_check": { + "name": "organization_setting_events_source_check", + "value": "\"organization_setting_events\".\"source\" IN ('dashboard')" + } + }, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "organizations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_org_id": { + "name": "github_org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'beta'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_github_org_id_idx": { + "name": "organizations_github_org_id_idx", + "columns": [ + { + "expression": "github_org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.private_monitor_check_failures": { + "name": "private_monitor_check_failures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "private_monitor_check_failures_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "run_id": { + "name": "run_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "recovered": { + "name": "recovered", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "private_monitor_check_failures_observed_idx": { + "name": "private_monitor_check_failures_observed_idx", + "columns": [ + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_monitor_check_failures_attempt_check": { + "name": "private_monitor_check_failures_attempt_check", + "value": "\"private_monitor_check_failures\".\"attempt\" > 0" + }, + "private_monitor_check_failures_key_nonempty": { + "name": "private_monitor_check_failures_key_nonempty", + "value": "length(btrim(\"private_monitor_check_failures\".\"key\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.private_monitor_incident_events": { + "name": "private_monitor_incident_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "private_monitor_incident_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transition": { + "name": "transition", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurrence_count": { + "name": "occurrence_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_detected_at": { + "name": "first_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "private_monitor_incident_events_occurred_idx": { + "name": "private_monitor_incident_events_occurred_idx", + "columns": [ + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_monitor_incident_events_transition_check": { + "name": "private_monitor_incident_events_transition_check", + "value": "\"private_monitor_incident_events\".\"transition\" IN ('opened', 'resolved')" + }, + "private_monitor_incident_events_severity_check": { + "name": "private_monitor_incident_events_severity_check", + "value": "\"private_monitor_incident_events\".\"severity\" IN ('warning', 'critical')" + }, + "private_monitor_incident_events_text_nonempty": { + "name": "private_monitor_incident_events_text_nonempty", + "value": "length(btrim(\"private_monitor_incident_events\".\"key\")) > 0 AND length(btrim(\"private_monitor_incident_events\".\"group\")) > 0 AND length(btrim(\"private_monitor_incident_events\".\"summary\")) > 0 AND \"private_monitor_incident_events\".\"occurrence_count\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.private_monitor_incidents": { + "name": "private_monitor_incidents", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opened_detail": { + "name": "opened_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "occurrence_count": { + "name": "occurrence_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "first_detected_at": { + "name": "first_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_detected_at": { + "name": "last_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pending_notification_key": { + "name": "pending_notification_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pending_notification_kind": { + "name": "pending_notification_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notification_attempts": { + "name": "notification_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "notification_available_at": { + "name": "notification_available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notification_lease_owner": { + "name": "notification_lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notification_lease_expires_at": { + "name": "notification_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_notified_at": { + "name": "last_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_notification_error": { + "name": "last_notification_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_notification_key": { + "name": "last_notification_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_delivery_receipt": { + "name": "last_delivery_receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "private_monitor_incidents_state_updated_idx": { + "name": "private_monitor_incidents_state_updated_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "private_monitor_incidents_notification_idx": { + "name": "private_monitor_incidents_notification_idx", + "columns": [ + { + "expression": "notification_available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notification_lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"private_monitor_incidents\".\"pending_notification_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_monitor_delivery_receipt_bound": { + "name": "private_monitor_delivery_receipt_bound", + "value": "(\"last_delivery_receipt\" IS NULL AND \"last_notification_key\" IS NULL) OR (\"last_delivery_receipt\" IS NOT NULL AND \"last_notification_key\" IS NOT NULL AND length(\"last_notification_key\") BETWEEN 1 AND 512 AND jsonb_typeof(\"last_delivery_receipt\") = 'object' AND octet_length(\"last_delivery_receipt\"::text) <= 2048)" + }, + "private_monitor_incidents_state_check": { + "name": "private_monitor_incidents_state_check", + "value": "\"private_monitor_incidents\".\"state\" IN ('open', 'resolved')" + }, + "private_monitor_incidents_severity_check": { + "name": "private_monitor_incidents_severity_check", + "value": "\"private_monitor_incidents\".\"severity\" IN ('warning', 'critical')" + }, + "private_monitor_incidents_notification_kind_check": { + "name": "private_monitor_incidents_notification_kind_check", + "value": "\"private_monitor_incidents\".\"pending_notification_kind\" IS NULL OR \"private_monitor_incidents\".\"pending_notification_kind\" IN ('opened', 'reminder', 'resolved')" + }, + "private_monitor_incidents_notification_pair_check": { + "name": "private_monitor_incidents_notification_pair_check", + "value": "(\"private_monitor_incidents\".\"pending_notification_key\" IS NULL) = (\"private_monitor_incidents\".\"pending_notification_kind\" IS NULL)" + }, + "private_monitor_incidents_occurrence_count_check": { + "name": "private_monitor_incidents_occurrence_count_check", + "value": "\"private_monitor_incidents\".\"occurrence_count\" > 0 AND \"private_monitor_incidents\".\"notification_attempts\" >= 0 AND \"private_monitor_incidents\".\"notification_attempts\" <= 5" + }, + "private_monitor_incidents_text_nonempty": { + "name": "private_monitor_incidents_text_nonempty", + "value": "length(btrim(\"private_monitor_incidents\".\"key\")) > 0 AND length(btrim(\"private_monitor_incidents\".\"group\")) > 0 AND length(btrim(\"private_monitor_incidents\".\"summary\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.private_monitor_runs": { + "name": "private_monitor_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "private_monitor_runs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "check_count": { + "name": "check_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_count": { + "name": "failure_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "private_monitor_runs_scheduled_idx": { + "name": "private_monitor_runs_scheduled_idx", + "columns": [ + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "private_monitor_runs_started_idx": { + "name": "private_monitor_runs_started_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_monitor_runs_status_check": { + "name": "private_monitor_runs_status_check", + "value": "\"private_monitor_runs\".\"status\" IN ('running', 'completed', 'failed')" + }, + "private_monitor_runs_counts_check": { + "name": "private_monitor_runs_counts_check", + "value": "\"private_monitor_runs\".\"check_count\" >= 0 AND \"private_monitor_runs\".\"failure_count\" >= 0 AND \"private_monitor_runs\".\"failure_count\" <= \"private_monitor_runs\".\"check_count\"" + }, + "private_monitor_runs_owner_nonempty": { + "name": "private_monitor_runs_owner_nonempty", + "value": "length(btrim(\"private_monitor_runs\".\"owner\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.private_monitor_state": { + "name": "private_monitor_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "heartbeat_delivery_error": { + "name": "heartbeat_delivery_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_monitor_state_singleton_check": { + "name": "private_monitor_state_singleton_check", + "value": "\"private_monitor_state\".\"id\" = 1" + } + }, + "isRLSEnabled": false + }, + "public.private_worker_rehearsals": { + "name": "private_worker_rehearsals", + "schema": "", + "columns": { + "nonce": { + "name": "nonce", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'armed'" + }, + "operator_github_id": { + "name": "operator_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "job_id": { + "name": "job_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_public_id": { + "name": "review_public_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "armed_at": { + "name": "armed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "interrupted_worker_instance": { + "name": "interrupted_worker_instance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replacement_worker_instance": { + "name": "replacement_worker_instance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replacement_observed_at": { + "name": "replacement_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "before_review_count": { + "name": "before_review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "before_usage_count": { + "name": "before_usage_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "before_check_count": { + "name": "before_check_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "before_publication_count": { + "name": "before_publication_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_review_count": { + "name": "after_review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_usage_count": { + "name": "after_usage_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_check_count": { + "name": "after_check_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_publication_count": { + "name": "after_publication_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "private_worker_rehearsals_review_idx": { + "name": "private_worker_rehearsals_review_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "private_worker_rehearsals_job_idx": { + "name": "private_worker_rehearsals_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "private_worker_rehearsals_state_idx": { + "name": "private_worker_rehearsals_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "private_worker_rehearsals_org_id_organizations_id_fk": { + "name": "private_worker_rehearsals_org_id_organizations_id_fk", + "tableFrom": "private_worker_rehearsals", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "private_worker_rehearsals_repository_id_repositories_id_fk": { + "name": "private_worker_rehearsals_repository_id_repositories_id_fk", + "tableFrom": "private_worker_rehearsals", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "private_worker_rehearsals_review_id_reviews_id_fk": { + "name": "private_worker_rehearsals_review_id_reviews_id_fk", + "tableFrom": "private_worker_rehearsals", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "private_worker_rehearsals_job_id_jobs_id_fk": { + "name": "private_worker_rehearsals_job_id_jobs_id_fk", + "tableFrom": "private_worker_rehearsals", + "tableTo": "jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "private_worker_rehearsals_state_check": { + "name": "private_worker_rehearsals_state_check", + "value": "\"private_worker_rehearsals\".\"state\" IN ('armed', 'awaiting_replacement', 'replacement_verified', 'completed', 'expired', 'failed')" + }, + "private_worker_rehearsals_identity_check": { + "name": "private_worker_rehearsals_identity_check", + "value": "length(btrim(\"private_worker_rehearsals\".\"org_slug\")) > 0 AND length(btrim(\"private_worker_rehearsals\".\"repo_full_name\")) > 0 AND \"private_worker_rehearsals\".\"pr_number\" > 0 AND \"private_worker_rehearsals\".\"head_sha\" ~ '^[0-9a-f]{40}$'" + }, + "private_worker_rehearsals_arming_window_check": { + "name": "private_worker_rehearsals_arming_window_check", + "value": "\"private_worker_rehearsals\".\"expires_at\" > \"private_worker_rehearsals\".\"armed_at\" AND \"private_worker_rehearsals\".\"expires_at\" <= \"private_worker_rehearsals\".\"armed_at\" + interval '10 minutes'" + }, + "private_worker_rehearsals_before_counts_check": { + "name": "private_worker_rehearsals_before_counts_check", + "value": "(\"private_worker_rehearsals\".\"before_review_count\" IS NULL AND \"private_worker_rehearsals\".\"before_usage_count\" IS NULL AND \"private_worker_rehearsals\".\"before_check_count\" IS NULL AND \"private_worker_rehearsals\".\"before_publication_count\" IS NULL) OR (\"private_worker_rehearsals\".\"before_review_count\" >= 0 AND \"private_worker_rehearsals\".\"before_usage_count\" >= 0 AND \"private_worker_rehearsals\".\"before_check_count\" >= 0 AND \"private_worker_rehearsals\".\"before_publication_count\" >= 0)" + }, + "private_worker_rehearsals_after_counts_check": { + "name": "private_worker_rehearsals_after_counts_check", + "value": "(\"private_worker_rehearsals\".\"after_review_count\" IS NULL AND \"private_worker_rehearsals\".\"after_usage_count\" IS NULL AND \"private_worker_rehearsals\".\"after_check_count\" IS NULL AND \"private_worker_rehearsals\".\"after_publication_count\" IS NULL) OR (\"private_worker_rehearsals\".\"after_review_count\" >= 0 AND \"private_worker_rehearsals\".\"after_usage_count\" >= 0 AND \"private_worker_rehearsals\".\"after_check_count\" >= 0 AND \"private_worker_rehearsals\".\"after_publication_count\" >= 0)" + }, + "private_worker_rehearsals_replacement_pair_check": { + "name": "private_worker_rehearsals_replacement_pair_check", + "value": "(\"private_worker_rehearsals\".\"replacement_worker_instance\" IS NULL) = (\"private_worker_rehearsals\".\"replacement_observed_at\" IS NULL)" + }, + "private_worker_rehearsals_consumed_state_check": { + "name": "private_worker_rehearsals_consumed_state_check", + "value": "(\"private_worker_rehearsals\".\"state\" IN ('armed', 'expired') AND \"private_worker_rehearsals\".\"consumed_at\" IS NULL AND \"private_worker_rehearsals\".\"interrupted_worker_instance\" IS NULL AND \"private_worker_rehearsals\".\"before_review_count\" IS NULL) OR (\"private_worker_rehearsals\".\"state\" IN ('awaiting_replacement', 'replacement_verified', 'completed', 'failed') AND \"private_worker_rehearsals\".\"consumed_at\" IS NOT NULL AND \"private_worker_rehearsals\".\"interrupted_worker_instance\" IS NOT NULL AND \"private_worker_rehearsals\".\"before_review_count\" IS NOT NULL)" + }, + "private_worker_rehearsals_replacement_state_check": { + "name": "private_worker_rehearsals_replacement_state_check", + "value": "(\"private_worker_rehearsals\".\"state\" IN ('armed', 'awaiting_replacement', 'expired') AND \"private_worker_rehearsals\".\"replacement_worker_instance\" IS NULL) OR (\"private_worker_rehearsals\".\"state\" IN ('replacement_verified', 'completed') AND \"private_worker_rehearsals\".\"replacement_worker_instance\" IS NOT NULL) OR \"private_worker_rehearsals\".\"state\" = 'failed'" + }, + "private_worker_rehearsals_completion_state_check": { + "name": "private_worker_rehearsals_completion_state_check", + "value": "(\"private_worker_rehearsals\".\"state\" = 'completed' AND \"private_worker_rehearsals\".\"after_review_count\" IS NOT NULL AND \"private_worker_rehearsals\".\"completed_at\" IS NOT NULL AND \"private_worker_rehearsals\".\"failure_reason\" IS NULL) OR (\"private_worker_rehearsals\".\"state\" IN ('expired', 'failed') AND \"private_worker_rehearsals\".\"after_review_count\" IS NULL AND \"private_worker_rehearsals\".\"completed_at\" IS NOT NULL AND \"private_worker_rehearsals\".\"failure_reason\" IS NOT NULL) OR (\"private_worker_rehearsals\".\"state\" IN ('armed', 'awaiting_replacement', 'replacement_verified') AND \"private_worker_rehearsals\".\"after_review_count\" IS NULL AND \"private_worker_rehearsals\".\"completed_at\" IS NULL AND \"private_worker_rehearsals\".\"failure_reason\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.release_steps": { + "name": "release_steps", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.repo_config_probes": { + "name": "repo_config_probes", + "schema": "", + "columns": { + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "files": { + "name": "files", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "repo_config_probes_repository_id_repositories_id_fk": { + "name": "repo_config_probes_repository_id_repositories_id_fk", + "tableFrom": "repo_config_probes", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.repositories": { + "name": "repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "repositories_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "installation_id": { + "name": "installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "repositories_installation_id_installations_id_fk": { + "name": "repositories_installation_id_installations_id_fk", + "tableFrom": "repositories", + "tableTo": "installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "repositories_github_repo_id_unique": { + "name": "repositories_github_repo_id_unique", + "nullsNotDistinct": false, + "columns": [ + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.repository_enablement_events": { + "name": "repository_enablement_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "repository_enablement_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "repository_full_name": { + "name": "repository_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_private": { + "name": "repository_private", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dashboard'" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "repository_enablement_events_org_time_idx": { + "name": "repository_enablement_events_org_time_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repository_enablement_events_repo_time_idx": { + "name": "repository_enablement_events_repo_time_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "repository_enablement_events_org_id_organizations_id_fk": { + "name": "repository_enablement_events_org_id_organizations_id_fk", + "tableFrom": "repository_enablement_events", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "repository_enablement_events_repository_id_repositories_id_fk": { + "name": "repository_enablement_events_repository_id_repositories_id_fk", + "tableFrom": "repository_enablement_events", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "repository_enablement_events_actor_user_id_users_id_fk": { + "name": "repository_enablement_events_actor_user_id_users_id_fk", + "tableFrom": "repository_enablement_events", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "repository_enablement_events_action_check": { + "name": "repository_enablement_events_action_check", + "value": "\"repository_enablement_events\".\"action\" IN ('enable', 'disable')" + }, + "repository_enablement_events_source_check": { + "name": "repository_enablement_events_source_check", + "value": "\"repository_enablement_events\".\"source\" IN ('dashboard', 'github_installation', 'github_pull_request', 'github_transfer', 'github_uninstall', 'migration_baseline')" + } + }, + "isRLSEnabled": false + }, + "public.repository_gate_enforcement": { + "name": "repository_gate_enforcement", + "schema": "", + "columns": { + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_protection": { + "name": "branch_protection", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_successful_at": { + "name": "last_successful_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "repository_gate_enforcement_status_checked_idx": { + "name": "repository_gate_enforcement_status_checked_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "repository_gate_enforcement_repository_id_repositories_id_fk": { + "name": "repository_gate_enforcement_repository_id_repositories_id_fk", + "tableFrom": "repository_gate_enforcement", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "repository_gate_enforcement_status_check": { + "name": "repository_gate_enforcement_status_check", + "value": "\"repository_gate_enforcement\".\"status\" IN ('required', 'not_required', 'unknown')" + }, + "repository_gate_enforcement_branch_protection_check": { + "name": "repository_gate_enforcement_branch_protection_check", + "value": "\"repository_gate_enforcement\".\"branch_protection\" IN ('protected', 'unprotected', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.respond_deliveries": { + "name": "respond_deliveries", + "schema": "", + "columns": { + "job_id": { + "name": "job_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "reservation_id": { + "name": "reservation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_org_id": { + "name": "source_org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_installation_id": { + "name": "source_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_installation_id": { + "name": "source_github_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_repo_id": { + "name": "source_github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_pr": { + "name": "is_pr", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "source_head_sha": { + "name": "source_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_nonce": { + "name": "marker_nonce", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reply_to_review_comment_id": { + "name": "reply_to_review_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "publication_identity_state": { + "name": "publication_identity_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'complete'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prepared'" + }, + "delivery_lease_expires_at": { + "name": "delivery_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github_comment_id": { + "name": "github_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "publication_lease_id": { + "name": "publication_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_lease_expires_at": { + "name": "publication_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "respond_deliveries_pending_idx": { + "name": "respond_deliveries_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "respond_deliveries_job_id_jobs_id_fk": { + "name": "respond_deliveries_job_id_jobs_id_fk", + "tableFrom": "respond_deliveries", + "tableTo": "jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "respond_deliveries_repository_id_repositories_id_fk": { + "name": "respond_deliveries_repository_id_repositories_id_fk", + "tableFrom": "respond_deliveries", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "respond_deliveries_reservation_id_hosted_usage_reservations_id_fk": { + "name": "respond_deliveries_reservation_id_hosted_usage_reservations_id_fk", + "tableFrom": "respond_deliveries", + "tableTo": "hosted_usage_reservations", + "columnsFrom": [ + "reservation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "respond_deliveries_state_check": { + "name": "respond_deliveries_state_check", + "value": "\"respond_deliveries\".\"state\" IN ('prepared', 'delivering', 'delivered', 'cancelled')" + }, + "respond_deliveries_issue_number_positive": { + "name": "respond_deliveries_issue_number_positive", + "value": "\"respond_deliveries\".\"issue_number\" > 0" + }, + "respond_deliveries_body_nonempty": { + "name": "respond_deliveries_body_nonempty", + "value": "length(btrim(\"respond_deliveries\".\"body\")) > 0" + }, + "respond_deliveries_publication_identity_state_check": { + "name": "respond_deliveries_publication_identity_state_check", + "value": "\"respond_deliveries\".\"publication_identity_state\" IN ('complete', 'legacy_delivered', 'cancelled_incomplete')" + }, + "respond_deliveries_publication_identity_check": { + "name": "respond_deliveries_publication_identity_check", + "value": "(\n \"respond_deliveries\".\"source_org_id\" IS NOT NULL\n AND \"respond_deliveries\".\"source_installation_id\" IS NOT NULL\n AND \"respond_deliveries\".\"source_github_installation_id\" IS NOT NULL\n AND \"respond_deliveries\".\"source_github_repo_id\" IS NOT NULL\n AND (NOT \"respond_deliveries\".\"is_pr\" OR \"respond_deliveries\".\"source_head_sha\" IS NOT NULL)\n )" + }, + "respond_deliveries_publication_identity_state_matches_row_check": { + "name": "respond_deliveries_publication_identity_state_matches_row_check", + "value": "(\n \"respond_deliveries\".\"publication_identity_state\" = 'complete'\n OR (\"respond_deliveries\".\"publication_identity_state\" = 'legacy_delivered' AND \"respond_deliveries\".\"state\" = 'delivered')\n OR (\"respond_deliveries\".\"publication_identity_state\" = 'cancelled_incomplete' AND \"respond_deliveries\".\"state\" = 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.review_feedback_control": { + "name": "review_feedback_control", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "review_feedback_control_singleton": { + "name": "review_feedback_control_singleton", + "value": "\"review_feedback_control\".\"id\" = 1" + }, + "review_feedback_control_mode": { + "name": "review_feedback_control_mode", + "value": "\"review_feedback_control\".\"mode\" IN ('inherit', 'enabled', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.review_feedback_polls": { + "name": "review_feedback_polls", + "schema": "", + "columns": { + "github_repo_id": { + "name": "github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "next_poll_at": { + "name": "next_poll_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "review_feedback_polls_github_repo_id_pr_number_pk": { + "name": "review_feedback_polls_github_repo_id_pr_number_pk", + "columns": [ + "github_repo_id", + "pr_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.review_feedback_requests": { + "name": "review_feedback_requests", + "schema": "", + "columns": { + "github_repo_id": { + "name": "github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "review_feedback_requests_org_created_idx": { + "name": "review_feedback_requests_org_created_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "review_feedback_requests_github_repo_id_pr_number_head_sha_digest_pk": { + "name": "review_feedback_requests_github_repo_id_pr_number_head_sha_digest_pk", + "columns": [ + "github_repo_id", + "pr_number", + "head_sha", + "digest" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.review_logs": { + "name": "review_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "review_logs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "line": { + "name": "line", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "review_logs_review_seq_idx": { + "name": "review_logs_review_seq_idx", + "columns": [ + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "review_logs_review_id_reviews_id_fk": { + "name": "review_logs_review_id_reviews_id_fk", + "tableFrom": "review_logs", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.review_publication_receipts": { + "name": "review_publication_receipts", + "schema": "", + "columns": { + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "receipt_version": { + "name": "receipt_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "receipt_id": { + "name": "receipt_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publication_channel": { + "name": "publication_channel", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_review_id": { + "name": "github_review_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "review_publication_receipts_review_id_reviews_id_fk": { + "name": "review_publication_receipts_review_id_reviews_id_fk", + "tableFrom": "review_publication_receipts", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "review_publication_receipts_identity_check": { + "name": "review_publication_receipts_identity_check", + "value": "(\"review_publication_receipts\".\"receipt_version\" IS NULL AND \"review_publication_receipts\".\"receipt_id\" IS NULL AND \"review_publication_receipts\".\"publication_channel\" IS NULL) OR (\"review_publication_receipts\".\"receipt_version\" = 1 AND length(btrim(\"review_publication_receipts\".\"receipt_id\")) BETWEEN 1 AND 200 AND (\"review_publication_receipts\".\"publication_channel\" IS NULL OR \"review_publication_receipts\".\"publication_channel\" = 'reviewComments')) OR (\"review_publication_receipts\".\"receipt_version\" = 2 AND length(btrim(\"review_publication_receipts\".\"receipt_id\")) BETWEEN 1 AND 200 AND \"review_publication_receipts\".\"publication_channel\" IS NOT NULL AND \"review_publication_receipts\".\"publication_channel\" IN ('reviewComments', 'checkAnnotations'))" + }, + "review_publication_receipts_github_review_id_check": { + "name": "review_publication_receipts_github_review_id_check", + "value": "\"review_publication_receipts\".\"github_review_id\" IS NULL OR \"review_publication_receipts\".\"github_review_id\" ~ '^[1-9][0-9]{0,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.reviews": { + "name": "reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "reviews_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "public_id": { + "name": "public_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "source_org_id": { + "name": "source_org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_installation_id": { + "name": "source_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_installation_id": { + "name": "source_github_installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_github_repo_id": { + "name": "source_github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_repo_full_name": { + "name": "source_repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "author_github_id": { + "name": "author_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_sha": { + "name": "base_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "since_sha": { + "name": "since_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "trigger_context": { + "name": "trigger_context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "review_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config_files": { + "name": "config_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "config_provenance": { + "name": "config_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "silent": { + "name": "silent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "engine_gate_failing": { + "name": "engine_gate_failing", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gate_failing": { + "name": "gate_failing", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "advisory_check_run_id": { + "name": "advisory_check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "gate_check_run_id": { + "name": "gate_check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "gate_sync_lease_id": { + "name": "gate_sync_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gate_sync_lease_expires_at": { + "name": "gate_sync_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publication_lifecycle_reconciled_at": { + "name": "publication_lifecycle_reconciled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publication_lifecycle_required_at": { + "name": "publication_lifecycle_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "origin_claim_receipt_id": { + "name": "origin_claim_receipt_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "reviews_public_id_idx": { + "name": "reviews_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reviews_repo_pr_idx": { + "name": "reviews_repo_pr_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reviews_status_idx": { + "name": "reviews_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reviews_running_started_at_idx": { + "name": "reviews_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"reviews\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "reviews_publication_lifecycle_pending_idx": { + "name": "reviews_publication_lifecycle_pending_idx", + "columns": [ + { + "expression": "finished_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"reviews\".\"status\" = 'completed' AND \"reviews\".\"publication_lifecycle_required_at\" IS NOT NULL AND \"reviews\".\"publication_lifecycle_reconciled_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reviews_repository_id_repositories_id_fk": { + "name": "reviews_repository_id_repositories_id_fk", + "tableFrom": "reviews", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reviews_origin_claim_receipt_id_job_attempt_receipts_receipt_id_fk": { + "name": "reviews_origin_claim_receipt_id_job_attempt_receipts_receipt_id_fk", + "tableFrom": "reviews", + "tableTo": "job_attempt_receipts", + "columnsFrom": [ + "origin_claim_receipt_id" + ], + "columnsTo": [ + "receipt_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reviews_trigger_source_check": { + "name": "reviews_trigger_source_check", + "value": "\"reviews\".\"trigger_source\" IN ('unknown', 'automatic_pull_request', 'requested_review', 'github_check_rerun', 'finding_feedback')" + }, + "reviews_trigger_context_check": { + "name": "reviews_trigger_context_check", + "value": "(\"reviews\".\"trigger_source\" = 'finding_feedback' AND \"reviews\".\"trigger_context\" IS NOT NULL AND jsonb_typeof(\"reviews\".\"trigger_context\") = 'object' AND \"reviews\".\"trigger_context\" - ARRAY['source', 'feedbackDigest']::text[] = '{}'::jsonb AND COALESCE(\"reviews\".\"trigger_context\"->>'source', '') = 'finding_feedback' AND jsonb_typeof(\"reviews\".\"trigger_context\"->'feedbackDigest') = 'string' AND COALESCE(\"reviews\".\"trigger_context\"->>'feedbackDigest', '') ~ '^[a-f0-9]{64}$') OR (\"reviews\".\"trigger_source\" = 'unknown' AND (\"reviews\".\"trigger_context\" IS NULL OR \"reviews\".\"trigger_context\" = '{\"source\":\"unknown\"}'::jsonb)) OR (\"reviews\".\"trigger_source\" <> 'unknown' AND \"reviews\".\"trigger_context\" IS NOT NULL AND jsonb_typeof(\"reviews\".\"trigger_context\") = 'object' AND \"reviews\".\"trigger_context\" - ARRAY['source', 'webhookDeliveryId', 'webhookEvent', 'webhookAction', 'sourceCommentId', 'sourceUrl', 'requestedByGithubId', 'requestedByLogin', 'checkName']::text[] = '{}'::jsonb AND \"reviews\".\"trigger_context\"->>'source' = \"reviews\".\"trigger_source\" AND jsonb_typeof(\"reviews\".\"trigger_context\"->'webhookDeliveryId') = 'string' AND COALESCE(length(btrim(\"reviews\".\"trigger_context\"->>'webhookDeliveryId')), 0) > 0 AND length(\"reviews\".\"trigger_context\"->>'webhookDeliveryId') <= 200 AND ((\"reviews\".\"trigger_source\" = 'automatic_pull_request' AND \"reviews\".\"trigger_context\"->>'webhookEvent' = 'pull_request') OR (\"reviews\".\"trigger_source\" = 'requested_review' AND \"reviews\".\"trigger_context\"->>'webhookEvent' IN ('issue_comment', 'pull_request_review_comment')) OR (\"reviews\".\"trigger_source\" = 'github_check_rerun' AND \"reviews\".\"trigger_context\"->>'webhookEvent' IN ('check_run', 'check_suite'))) AND (NOT \"reviews\".\"trigger_context\" ? 'webhookAction' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'webhookAction') = 'string' AND length(\"reviews\".\"trigger_context\"->>'webhookAction') <= 100)) AND (NOT \"reviews\".\"trigger_context\" ? 'sourceCommentId' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'sourceCommentId') = 'number' AND (\"reviews\".\"trigger_context\"->>'sourceCommentId')::numeric = trunc((\"reviews\".\"trigger_context\"->>'sourceCommentId')::numeric) AND (\"reviews\".\"trigger_context\"->>'sourceCommentId')::numeric BETWEEN 1 AND 9007199254740991)) AND (NOT \"reviews\".\"trigger_context\" ? 'sourceUrl' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'sourceUrl') = 'string' AND length(\"reviews\".\"trigger_context\"->>'sourceUrl') <= 2048 AND \"reviews\".\"trigger_context\"->>'sourceUrl' ~* '^https://github[.]com([/?#]|$)')) AND (NOT \"reviews\".\"trigger_context\" ? 'requestedByGithubId' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'requestedByGithubId') = 'number' AND (\"reviews\".\"trigger_context\"->>'requestedByGithubId')::numeric = trunc((\"reviews\".\"trigger_context\"->>'requestedByGithubId')::numeric) AND (\"reviews\".\"trigger_context\"->>'requestedByGithubId')::numeric BETWEEN 1 AND 9007199254740991)) AND (NOT \"reviews\".\"trigger_context\" ? 'requestedByLogin' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'requestedByLogin') = 'string' AND length(\"reviews\".\"trigger_context\"->>'requestedByLogin') <= 100)) AND (NOT \"reviews\".\"trigger_context\" ? 'checkName' OR (jsonb_typeof(\"reviews\".\"trigger_context\"->'checkName') = 'string' AND length(\"reviews\".\"trigger_context\"->>'checkName') <= 200)))" + } + }, + "isRLSEnabled": false + }, + "public.self_service_trial_grants": { + "name": "self_service_trial_grants", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "initiated_by_github_id": { + "name": "initiated_by_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "requested_mode": { + "name": "requested_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_mode": { + "name": "granted_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "self_service_trial_grants_actor_created_idx": { + "name": "self_service_trial_grants_actor_created_idx", + "columns": [ + { + "expression": "initiated_by_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "self_service_trial_grants_requested_mode_check": { + "name": "self_service_trial_grants_requested_mode_check", + "value": "\"self_service_trial_grants\".\"requested_mode\" IN ('hosted', 'byok')" + }, + "self_service_trial_grants_granted_mode_check": { + "name": "self_service_trial_grants_granted_mode_check", + "value": "\"self_service_trial_grants\".\"granted_mode\" IN ('hosted', 'byok')" + } + }, + "isRLSEnabled": false + }, + "public.service_heartbeats": { + "name": "service_heartbeats", + "schema": "", + "columns": { + "component": { + "name": "component", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "service_heartbeats_component_check": { + "name": "service_heartbeats_component_check", + "value": "\"service_heartbeats\".\"component\" IN ('worker', 'monitor', 'monitor-heartbeat-delivery')" + }, + "service_heartbeats_instance_nonempty": { + "name": "service_heartbeats_instance_nonempty", + "value": "length(btrim(\"service_heartbeats\".\"instance_id\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "github_access_token_ciphertext": { + "name": "github_access_token_ciphertext", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "membership_checked_at": { + "name": "membership_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "membership_check_available_at": { + "name": "membership_check_available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_events": { + "name": "usage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "usage_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_micros": { + "name": "cost_micros", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "billing_scope": { + "name": "billing_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "usage_events_org_id_organizations_id_fk": { + "name": "usage_events_org_id_organizations_id_fk", + "tableFrom": "usage_events", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_events_repository_id_repositories_id_fk": { + "name": "usage_events_repository_id_repositories_id_fk", + "tableFrom": "usage_events", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_events_review_id_reviews_id_fk": { + "name": "usage_events_review_id_reviews_id_fk", + "tableFrom": "usage_events", + "tableTo": "reviews", + "columnsFrom": [ + "review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_events_cost_micros_nonnegative": { + "name": "usage_events_cost_micros_nonnegative", + "value": "\"usage_events\".\"cost_micros\" IS NULL OR \"usage_events\".\"cost_micros\" >= 0" + }, + "usage_events_cost_cents_nonnegative": { + "name": "usage_events_cost_cents_nonnegative", + "value": "\"usage_events\".\"cost_cents\" IS NULL OR \"usage_events\".\"cost_cents\" >= 0" + }, + "usage_events_billing_scope_check": { + "name": "usage_events_billing_scope_check", + "value": "\"usage_events\".\"billing_scope\" IN ('analytics', 'private_hosted')" + }, + "usage_events_trigger_source_check": { + "name": "usage_events_trigger_source_check", + "value": "\"usage_events\".\"trigger_source\" IN ('unknown', 'automatic_pull_request', 'requested_review', 'github_check_rerun', 'finding_feedback', 'github_mention')" + } + }, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "users_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "github_id": { + "name": "github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "login": { + "name": "login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_github_id_unique": { + "name": "users_github_id_unique", + "nullsNotDistinct": false, + "columns": [ + "github_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_deliveries": { + "name": "webhook_deliveries", + "schema": "", + "columns": { + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "webhook_deliveries_completed_at_idx": { + "name": "webhook_deliveries_completed_at_idx", + "columns": [ + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook_deliveries\".\"completed_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_deliveries_payload_completion_check": { + "name": "webhook_deliveries_payload_completion_check", + "value": "(\"webhook_deliveries\".\"payload\" IS NULL) = (\"webhook_deliveries\".\"completed_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.job_attempt_receipts": { + "name": "job_attempt_receipts", + "schema": "", + "columns": { + "receipt_id": { + "name": "receipt_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_epoch": { + "name": "source_epoch", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "claim_receipt_id": { + "name": "claim_receipt_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "claim_kind": { + "name": "claim_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claim'" + }, + "binding_receipt_id": { + "name": "binding_receipt_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "binding_kind": { + "name": "binding_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'review_bound'" + }, + "job_id": { + "name": "job_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "job_kind": { + "name": "job_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_id": { + "name": "review_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "review_public_id": { + "name": "review_public_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovered": { + "name": "recovered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "recovery": { + "name": "recovery", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "clock_timestamp()" + } + }, + "indexes": { + "job_attempt_one_closure": { + "name": "job_attempt_one_closure", + "columns": [ + { + "expression": "claim_receipt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"job_attempt_receipts\".\"event_kind\" = 'queue_closed'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_attempt_one_binding": { + "name": "job_attempt_one_binding", + "columns": [ + { + "expression": "claim_receipt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"job_attempt_receipts\".\"event_kind\" = 'review_bound'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_attempt_one_observation": { + "name": "job_attempt_one_observation", + "columns": [ + { + "expression": "claim_receipt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"job_attempt_receipts\".\"event_kind\" = 'review_observed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_attempt_receipts_predecessor_id_job_attempt_receipts_receipt_id_fk": { + "name": "job_attempt_receipts_predecessor_id_job_attempt_receipts_receipt_id_fk", + "tableFrom": "job_attempt_receipts", + "tableTo": "job_attempt_receipts", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "receipt_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "job_attempt_receipts_claim_receipt_id_job_id_ordinal_job_kind_claim_kind_job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_fk": { + "name": "job_attempt_receipts_claim_receipt_id_job_id_ordinal_job_kind_claim_kind_job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_fk", + "tableFrom": "job_attempt_receipts", + "tableTo": "job_attempt_receipts", + "columnsFrom": [ + "claim_receipt_id", + "job_id", + "ordinal", + "job_kind", + "claim_kind" + ], + "columnsTo": [ + "receipt_id", + "job_id", + "ordinal", + "job_kind", + "event_kind" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "job_attempt_receipts_binding_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_binding_kind_job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_fk": { + "name": "job_attempt_receipts_binding_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_binding_kind_job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_fk", + "tableFrom": "job_attempt_receipts", + "tableTo": "job_attempt_receipts", + "columnsFrom": [ + "binding_receipt_id", + "claim_receipt_id", + "job_id", + "review_id", + "review_public_id", + "binding_kind" + ], + "columnsTo": [ + "receipt_id", + "claim_receipt_id", + "job_id", + "review_id", + "review_public_id", + "event_kind" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "job_attempt_receipts_predecessor_id_unique": { + "name": "job_attempt_receipts_predecessor_id_unique", + "nullsNotDistinct": false, + "columns": [ + "predecessor_id" + ] + }, + "job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_unique": { + "name": "job_attempt_receipts_receipt_id_job_id_ordinal_job_kind_event_kind_unique", + "nullsNotDistinct": false, + "columns": [ + "receipt_id", + "job_id", + "ordinal", + "job_kind", + "event_kind" + ] + }, + "job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_unique": { + "name": "job_attempt_receipts_receipt_id_claim_receipt_id_job_id_review_id_review_public_id_event_kind_unique", + "nullsNotDistinct": false, + "columns": [ + "receipt_id", + "claim_receipt_id", + "job_id", + "review_id", + "review_public_id", + "event_kind" + ] + } + }, + "policies": {}, + "checkConstraints": { + "job_attempt_receipt_check_1": { + "name": "job_attempt_receipt_check_1", + "value": "schema_version = 1" + }, + "job_attempt_receipt_check_2": { + "name": "job_attempt_receipt_check_2", + "value": "claim_kind = 'claim'" + }, + "job_attempt_receipt_check_3": { + "name": "job_attempt_receipt_check_3", + "value": "binding_kind = 'review_bound'" + }, + "job_attempt_receipt_check_4": { + "name": "job_attempt_receipt_check_4", + "value": "job_kind IN ('review','review-feedback')" + }, + "job_attempt_receipt_check_5": { + "name": "job_attempt_receipt_check_5", + "value": "event_kind IN ('genesis','claim','review_bound','review_observed','incident','queue_closed')" + }, + "job_attempt_receipt_check_6": { + "name": "job_attempt_receipt_check_6", + "value": "phase IN ('planner','review','scorer','respond')" + }, + "job_attempt_receipt_check_7": { + "name": "job_attempt_receipt_check_7", + "value": "category IN ('providerError','invalidOutput','timeout','deadline')" + }, + "job_attempt_receipt_check_8": { + "name": "job_attempt_receipt_check_8", + "value": "recovery IN ('repair','fallback')" + }, + "job_attempt_receipt_check_9": { + "name": "job_attempt_receipt_check_9", + "value": "isfinite(occurred_at)" + }, + "job_attempt_receipt_check_10": { + "name": "job_attempt_receipt_check_10", + "value": "\n (event_kind = 'genesis' AND receipt_id = 0 AND source_epoch IS NOT NULL\n AND predecessor_id IS NULL AND claim_receipt_id IS NULL AND binding_receipt_id IS NULL\n AND job_id IS NULL AND ordinal IS NULL AND job_kind IS NULL AND review_id IS NULL\n AND review_public_id IS NULL AND outcome IS NULL AND phase IS NULL AND category IS NULL\n AND recovered IS NULL AND recovery IS NULL)\n OR\n (event_kind <> 'genesis' AND receipt_id > 0 AND source_epoch IS NULL\n AND predecessor_id IS NOT NULL AND predecessor_id < receipt_id\n AND claim_receipt_id IS NOT NULL AND claim_receipt_id <= receipt_id\n AND job_id IS NOT NULL AND job_id > 0 AND ordinal IS NOT NULL AND ordinal > 0\n AND job_kind IS NOT NULL AND outcome IS NOT NULL)\n " + }, + "job_attempt_receipt_check_11": { + "name": "job_attempt_receipt_check_11", + "value": "(review_id IS NULL) = (review_public_id IS NULL)" + }, + "job_attempt_receipt_check_12": { + "name": "job_attempt_receipt_check_12", + "value": "review_id IS NULL OR review_id > 0" + }, + "job_attempt_receipt_check_13": { + "name": "job_attempt_receipt_check_13", + "value": "\n (event_kind = 'genesis') OR\n (event_kind = 'claim' AND claim_receipt_id = receipt_id AND review_id IS NULL AND outcome = 'started') OR\n (event_kind = 'review_bound' AND review_id IS NOT NULL AND outcome = 'bound') OR\n (event_kind = 'review_observed' AND review_id IS NOT NULL AND outcome IN ('staged','completed','failed','interrupted','cancelled')) OR\n (event_kind = 'incident' AND review_id IS NOT NULL AND outcome = 'observed') OR\n (event_kind = 'queue_closed' AND review_id IS NULL AND outcome IN ('succeeded','retry','failed','continued','interrupted','cancelled','coalesced','deferred'))\n " + }, + "job_attempt_receipt_check_14": { + "name": "job_attempt_receipt_check_14", + "value": "(binding_receipt_id IS NOT NULL) = (event_kind IN ('review_observed','incident'))" + }, + "job_attempt_receipt_check_15": { + "name": "job_attempt_receipt_check_15", + "value": "\n (event_kind = 'incident' AND phase IS NOT NULL AND category IS NOT NULL AND recovered IS NOT NULL\n AND recovered = (recovery IS NOT NULL))\n OR (event_kind <> 'incident' AND phase IS NULL AND category IS NULL AND recovered IS NULL AND recovery IS NULL)\n " + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "public.finding_approval_role": { + "name": "finding_approval_role", + "schema": "public", + "values": [ + "member", + "admin" + ] + }, + "public.finding_approval_source": { + "name": "finding_approval_source", + "schema": "public", + "values": [ + "github", + "dashboard" + ] + }, + "public.finding_approval_verb": { + "name": "finding_approval_verb", + "schema": "public", + "values": [ + "approve", + "dismiss" + ] + }, + "public.job_status": { + "name": "job_status", + "schema": "public", + "values": [ + "queued", + "running", + "done", + "failed" + ] + }, + "public.review_status": { + "name": "review_status", + "schema": "public", + "values": [ + "queued", + "running", + "completed", + "failed", + "stale" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 53d30b9f..b2edf90c 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -442,6 +442,13 @@ "when": 1790186892052, "tag": "0063_review_feedback_control", "breakpoints": true + }, + { + "idx": 63, + "version": "7", + "when": 1792355455226, + "tag": "0064_job_attempt_receipts", + "breakpoints": true } ] } diff --git a/scripts/inspect-review-ledger.ts b/scripts/inspect-review-ledger.ts index cfc94cc3..5d80b7af 100644 --- a/scripts/inspect-review-ledger.ts +++ b/scripts/inspect-review-ledger.ts @@ -1,3 +1,5 @@ +import { Client } from "pg"; +import { readAttemptReceiptPageSql } from "@/lib/job-attempt-receipts"; import { closeDb, getPool } from "@/lib/db"; /** @@ -7,7 +9,25 @@ import { closeDb, getPool } from "@/lib/db"; * twelve characters, so this is the scriptable way to read the full value. * Read-only. */ +export async function readAttemptReceipts(args: string[]): Promise { + if (args.length !== 2 || args[0] !== "--attempt-receipts" || !/^(0|[1-9][0-9]{0,18})$/.test(args[1]!)) { + throw new Error("usage: --attempt-receipts "); + } + const client = new Client({ connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 2000, query_timeout: 5000, + options: "-c default_transaction_read_only=on -c statement_timeout=5000 -c lock_timeout=250" }); + try { + await client.connect(); + const result = await client.query(readAttemptReceiptPageSql, [args[1]]); + const output = JSON.stringify({ rows: result.rows }); + if (Buffer.byteLength(output) > 65536) throw new Error("receipt page exceeds response bound"); + console.log(output); + } finally { await client.end(); } +} + async function main(): Promise { + const args = process.argv.slice(2); + if (args.length) { await readAttemptReceipts(args); return; } const pool = getPool(); const reviews = await pool.query( `SELECT r.id, r.public_id, r.status, r.queued_at, r.started_at, @@ -42,7 +62,7 @@ async function main(): Promise { await closeDb(); } -main().catch((error) => { - console.error(error); +if (import.meta.main) main().catch(() => { + console.error("review inspection unavailable"); process.exit(1); }); diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index a78257b6..d9997e36 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -5,6 +5,8 @@ import { check, customType, index, + foreignKey, + unique, integer, jsonb, pgEnum, @@ -21,6 +23,11 @@ import type { Envelope } from "@/lib/envelope"; import type { ReviewConfigProvenance } from "@/lib/github/contents"; import type { ReviewTriggerContext } from "@/lib/review-trigger"; +/** Decimal receipt identities preserve all PostgreSQL bigint bits. */ +const receiptIdentity = customType<{ data: string; driverData: string }>({ + dataType() { return "bigint"; }, +}); + /** Raw bytes column for AES-256-GCM sealed secrets. */ const bytea = customType<{ data: Buffer; driverData: Buffer }>({ dataType() { @@ -283,6 +290,7 @@ export const repositoryEnablementEvents = pgTable( export const reviews = pgTable( "reviews", { + originClaimReceiptId: receiptIdentity("origin_claim_receipt_id").references(() => jobAttemptReceipts.receiptId), id: bigint("id", { mode: "number" }) .primaryKey() .generatedAlwaysAsIdentity(), @@ -2112,6 +2120,8 @@ export type JobPayload = Record; export const jobs = pgTable( "jobs", { + activeClaimReceiptId: receiptIdentity("active_claim_receipt_id").references(() => jobAttemptReceipts.receiptId), + stagedReviewId: receiptIdentity("staged_review_id"), id: bigint("id", { mode: "number" }) .primaryKey() .generatedAlwaysAsIdentity(), @@ -2622,3 +2632,77 @@ export const orgConfigSnapshots = pgTable("org_config_snapshots", { .notNull() .defaultNow(), }); + +/** Immutable queue claims and finite review observations. */ +export const jobAttemptReceipts = pgTable("job_attempt_receipts", { + receiptId: receiptIdentity("receipt_id").notNull().primaryKey(), + predecessorId: receiptIdentity("predecessor_id"), + sourceEpoch: uuid("source_epoch"), + schemaVersion: integer("schema_version").notNull().default(1), + claimReceiptId: receiptIdentity("claim_receipt_id"), + claimKind: text("claim_kind").notNull().default("claim"), + bindingReceiptId: receiptIdentity("binding_receipt_id"), + bindingKind: text("binding_kind").notNull().default("review_bound"), + jobId: receiptIdentity("job_id"), + ordinal: integer("ordinal"), + jobKind: text("job_kind"), + reviewId: receiptIdentity("review_id"), + reviewPublicId: uuid("review_public_id"), + eventKind: text("event_kind").notNull(), + outcome: text("outcome"), + phase: text("phase"), + category: text("category"), + recovered: boolean("recovered"), + recovery: text("recovery"), + occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().default(sql`clock_timestamp()`), +}, (t) => [ + unique().on(t.predecessorId), + unique().on(t.receiptId, t.jobId, t.ordinal, t.jobKind, t.eventKind), + unique().on(t.receiptId, t.claimReceiptId, t.jobId, t.reviewId, t.reviewPublicId, t.eventKind), + foreignKey({ columns: [t.predecessorId], foreignColumns: [t.receiptId] }), + foreignKey({ columns: [t.claimReceiptId, t.jobId, t.ordinal, t.jobKind, t.claimKind], + foreignColumns: [t.receiptId, t.jobId, t.ordinal, t.jobKind, t.eventKind] }), + foreignKey({ columns: [t.bindingReceiptId, t.claimReceiptId, t.jobId, t.reviewId, t.reviewPublicId, t.bindingKind], + foreignColumns: [t.receiptId, t.claimReceiptId, t.jobId, t.reviewId, t.reviewPublicId, t.eventKind] }), + uniqueIndex("job_attempt_one_closure").on(t.claimReceiptId).where(sql`${t.eventKind} = 'queue_closed'`), + uniqueIndex("job_attempt_one_binding").on(t.claimReceiptId, t.reviewId).where(sql`${t.eventKind} = 'review_bound'`), + uniqueIndex("job_attempt_one_observation").on(t.claimReceiptId, t.reviewId, t.outcome).where(sql`${t.eventKind} = 'review_observed'`), + check("job_attempt_receipt_check_1", sql`schema_version = 1`), + check("job_attempt_receipt_check_2", sql`claim_kind = 'claim'`), + check("job_attempt_receipt_check_3", sql`binding_kind = 'review_bound'`), + check("job_attempt_receipt_check_4", sql`job_kind IN ('review','review-feedback')`), + check("job_attempt_receipt_check_5", sql`event_kind IN ('genesis','claim','review_bound','review_observed','incident','queue_closed')`), + check("job_attempt_receipt_check_6", sql`phase IN ('planner','review','scorer','respond')`), + check("job_attempt_receipt_check_7", sql`category IN ('providerError','invalidOutput','timeout','deadline')`), + check("job_attempt_receipt_check_8", sql`recovery IN ('repair','fallback')`), + check("job_attempt_receipt_check_9", sql`isfinite(occurred_at)`), + check("job_attempt_receipt_check_10", sql` + (event_kind = 'genesis' AND receipt_id = 0 AND source_epoch IS NOT NULL + AND predecessor_id IS NULL AND claim_receipt_id IS NULL AND binding_receipt_id IS NULL + AND job_id IS NULL AND ordinal IS NULL AND job_kind IS NULL AND review_id IS NULL + AND review_public_id IS NULL AND outcome IS NULL AND phase IS NULL AND category IS NULL + AND recovered IS NULL AND recovery IS NULL) + OR + (event_kind <> 'genesis' AND receipt_id > 0 AND source_epoch IS NULL + AND predecessor_id IS NOT NULL AND predecessor_id < receipt_id + AND claim_receipt_id IS NOT NULL AND claim_receipt_id <= receipt_id + AND job_id IS NOT NULL AND job_id > 0 AND ordinal IS NOT NULL AND ordinal > 0 + AND job_kind IS NOT NULL AND outcome IS NOT NULL) + `), + check("job_attempt_receipt_check_11", sql`(review_id IS NULL) = (review_public_id IS NULL)`), + check("job_attempt_receipt_check_12", sql`review_id IS NULL OR review_id > 0`), + check("job_attempt_receipt_check_13", sql` + (event_kind = 'genesis') OR + (event_kind = 'claim' AND claim_receipt_id = receipt_id AND review_id IS NULL AND outcome = 'started') OR + (event_kind = 'review_bound' AND review_id IS NOT NULL AND outcome = 'bound') OR + (event_kind = 'review_observed' AND review_id IS NOT NULL AND outcome IN ('staged','completed','failed','interrupted','cancelled')) OR + (event_kind = 'incident' AND review_id IS NOT NULL AND outcome = 'observed') OR + (event_kind = 'queue_closed' AND review_id IS NULL AND outcome IN ('succeeded','retry','failed','continued','interrupted','cancelled','coalesced','deferred')) + `), + check("job_attempt_receipt_check_14", sql`(binding_receipt_id IS NOT NULL) = (event_kind IN ('review_observed','incident'))`), + check("job_attempt_receipt_check_15", sql` + (event_kind = 'incident' AND phase IS NOT NULL AND category IS NOT NULL AND recovered IS NOT NULL + AND recovered = (recovery IS NOT NULL)) + OR (event_kind <> 'incident' AND phase IS NULL AND category IS NULL AND recovered IS NULL AND recovery IS NULL) + `), +]); diff --git a/src/lib/github/webhook-handler.ts b/src/lib/github/webhook-handler.ts index 130ed576..f3aae2ca 100644 --- a/src/lib/github/webhook-handler.ts +++ b/src/lib/github/webhook-handler.ts @@ -8,6 +8,11 @@ import { enqueueCustomerNotification, installationRemovedNotification, } from "@/lib/customer-notifications"; +import { + appendPreparedReceiptBatch, + prepareReviewObservation, + transactionReceiptClient, +} from "@/lib/job-attempt-receipts"; import { getDb, getPool, schema, type Database } from "@/lib/db"; import { requireEnv } from "@/lib/env"; import { getInstallationToken } from "@/lib/github/app-auth"; @@ -577,6 +582,7 @@ async function completeRunningReviewsForRemovedRepos( const stuck = await db .select({ id: schema.reviews.id, + originClaimReceiptId: schema.reviews.originClaimReceiptId, publicId: schema.reviews.publicId, advisoryCheckRunId: schema.reviews.advisoryCheckRunId, gateCheckRunId: schema.reviews.gateCheckRunId, @@ -612,10 +618,18 @@ async function completeRunningReviewsForRemovedRepos( return; } for (const review of stuck) { - await db - .update(schema.reviews) - .set({ status: "failed", errorMessage: message, finishedAt: new Date() }) - .where(and(eq(schema.reviews.id, review.id), eq(schema.reviews.status, "running"))); + const update = async (tx: Database) => { + const rows = await tx.update(schema.reviews) + .set({ status: "failed", errorMessage: message, finishedAt: new Date() }) + .where(and(eq(schema.reviews.id, review.id), eq(schema.reviews.status, "running"))) + .returning({ id: schema.reviews.id }); + if (rows.length && review.originClaimReceiptId) { + const client = transactionReceiptClient(tx); + await appendPreparedReceiptBatch(client, await prepareReviewObservation(client, review.id, "failed")); + } + }; + if (review.originClaimReceiptId) await db.transaction((tx) => update(tx as Database)); + else await update(db); await failCheckRuns( token, review.repoFullName, diff --git a/src/lib/hosted-review-pause.ts b/src/lib/hosted-review-pause.ts index 4f2c6c48..c2377863 100644 --- a/src/lib/hosted-review-pause.ts +++ b/src/lib/hosted-review-pause.ts @@ -1,6 +1,14 @@ import { and, eq, inArray, sql } from "drizzle-orm"; import type { Database } from "@/lib/db"; +import { + appendPreparedReceiptBatch, + claimEvent, + lockReceiptClaim, + prepareReviewObservation, + transactionReceiptClient, + type ReviewClaimLease, +} from "@/lib/job-attempt-receipts"; import { schema } from "@/lib/db"; import { checkRunExternalId } from "@/lib/github/checks"; import { reviewDetailsUrl } from "@/lib/oauth"; @@ -33,6 +41,7 @@ export async function claimPausedHostedReview( repoFullName: string; orgSlug?: string | null; checkRunsMayExist?: boolean; + reviewClaim?: ReviewClaimLease; }, finishedAt = new Date(), ): Promise<{ @@ -42,6 +51,10 @@ export async function claimPausedHostedReview( gateCheckExternalId: string; } | null> { return db.transaction(async (tx) => { + const client = transactionReceiptClient(tx); + const lease = publication.reviewClaim; + const job = lease?.receiptClaim ? await lockReceiptClaim(client, lease) : null; + if (lease?.receiptClaim && !job?.owned) return null; const claimKey = `postil:unavailable-review:${values.repositoryId}:${values.prNumber}:${values.headSha}`; await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtextextended(${claimKey}, 0))`); const existing = await tx @@ -63,6 +76,7 @@ export async function claimPausedHostedReview( id: schema.reviews.id, publicId: schema.reviews.publicId, headSha: schema.reviews.headSha, + originClaimReceiptId: schema.reviews.originClaimReceiptId, advisoryCheckRunId: schema.reviews.advisoryCheckRunId, gateCheckRunId: schema.reviews.gateCheckRunId, }) @@ -107,6 +121,7 @@ export async function claimPausedHostedReview( .insert(schema.reviews) .values({ ...values, + originClaimReceiptId: lease?.receiptClaim?.receiptId ?? null, status: "failed", errorMessage: HOSTED_REVIEW_UNAVAILABLE_MESSAGE, finishedAt, @@ -136,6 +151,17 @@ export async function claimPausedHostedReview( runAfter: new Date(finishedAt.getTime() + 30_000), maxAttempts: 5, }); + const events = []; + for (const row of active) { + if (row.originClaimReceiptId) events.push(...await prepareReviewObservation(client, row.id, "cancelled")); + } + if (lease?.receiptClaim) { + events.push(claimEvent(lease.receiptClaim.jobId, Number(job!.attempts), lease.receiptClaim.receiptId, + "review_bound", "bound", String(review.id), review.publicId, String(job!.kind))); + events.push(claimEvent(lease.receiptClaim.jobId, Number(job!.attempts), lease.receiptClaim.receiptId, + "review_observed", "failed", String(review.id), review.publicId, String(job!.kind))); + } + await appendPreparedReceiptBatch(client, events); return { ...review, advisoryCheckExternalId, gateCheckExternalId }; }); } diff --git a/src/lib/job-attempt-receipts.ts b/src/lib/job-attempt-receipts.ts new file mode 100644 index 00000000..4db325da --- /dev/null +++ b/src/lib/job-attempt-receipts.ts @@ -0,0 +1,383 @@ +import { sql, type SQL } from "drizzle-orm"; +import type { Pool, PoolClient } from "pg"; + +/** SQL transaction tails for explicitly bound review claims. */ +export interface ReceiptClient { + query(text: string, values?: unknown[]): Promise<{ rows: Record[] }>; +} + +export interface PreparedReceipt { + jobId: string; + ordinal: number; + jobKind: "review" | "review-feedback"; + claimId: string | null; + reviewId: string | null; + reviewPublicId: string | null; + eventKind: "claim" | "review_bound" | "review_observed" | "incident" | "queue_closed"; + outcome: "started" | "bound" | "staged" | "completed" | "failed" | "observed" + | "succeeded" | "retry" | "continued" | "interrupted" | "cancelled" | "coalesced" | "deferred"; + phase: "planner" | "review" | "scorer" | "respond" | null; + category: "providerError" | "invalidOutput" | "timeout" | "deadline" | null; + recovered: boolean | null; + recovery: "repair" | "fallback" | null; +} + +export const receiptAppendLockSql = "SELECT pg_advisory_xact_lock(17491, 1)"; +export const receiptHeadSql = + "SELECT r.receipt_id::text FROM job_attempt_receipts r ORDER BY r.receipt_id DESC LIMIT 1"; + +const receiptInsertSql = ` +WITH allocated AS ( + SELECT nextval('job_attempt_receipt_ids') AS id +) +INSERT INTO job_attempt_receipts ( + receipt_id, predecessor_id, claim_receipt_id, job_id, ordinal, job_kind, + review_id, review_public_id, event_kind, outcome, phase, category, recovered, + recovery, binding_receipt_id +) +SELECT id, $1::bigint, COALESCE($2::bigint, id), $3::bigint, $4::integer, $5, + $6::bigint, $7::uuid, $8, $9, $10, $11, $12::boolean, $13, + CASE WHEN $8 IN ('review_observed', 'incident') THEN ( + SELECT receipt_id FROM job_attempt_receipts + WHERE claim_receipt_id = $2::bigint AND review_id = $6::bigint + AND event_kind = 'review_bound' + ) END +FROM allocated +RETURNING receipt_id::text`; + +/** + * The caller owns a READ COMMITTED transaction and all business locks. Prepare + * and validate business state before calling; COMMIT immediately after return, + * except for assigning a new claim ID to its already locked job row. On error + * ROLLBACK the entire transaction. An uncertain COMMIT requires reconciliation. + */ +export async function appendPreparedReceiptBatch( + client: ReceiptClient, + events: readonly PreparedReceipt[], +): Promise { + const prepared = events.map((event) => { + // Reject accessors before reading any value. Only primitive domain fields + // enter the SQL arguments; an exception message or envelope is never read. + const fields = ["jobId", "ordinal", "jobKind", "claimId", "reviewId", + "reviewPublicId", "eventKind", "outcome", "phase", "category", + "recovered", "recovery"] as const; + const descriptors = Object.getOwnPropertyDescriptors(event); + if (Reflect.ownKeys(descriptors).length !== fields.length || fields.some( + (field) => !descriptors[field] || !("value" in descriptors[field]!), + )) throw new Error("invalid receipt projection"); + const value = Object.fromEntries(fields.map((field) => [field, descriptors[field]!.value])); + for (const field of fields) { + if (value[field] !== null && !["string", "number", "boolean"].includes(typeof value[field])) { + throw new Error("invalid receipt projection"); + } + } + for (const field of ["jobId", "claimId", "reviewId"]) { + if (value[field] !== null && (typeof value[field] !== "string" || !/^[1-9][0-9]{0,18}$/.test(value[field]))) { + throw new Error("invalid receipt identity"); + } + } + if (value.jobId === null || typeof value.ordinal !== "number" || !Number.isSafeInteger(value.ordinal) || value.ordinal < 1) { + throw new Error("invalid receipt identity"); + } + return fields.map((field) => value[field]); + }); + if (prepared.length === 0) return []; + const isolation = await client.query("SHOW transaction_isolation"); + if (isolation.rows[0]?.transaction_isolation !== "read committed") { + throw new Error("receipt transaction requires read committed"); + } + // Separate statements are intentional: the predecessor snapshot is acquired + // after the append lock wait, never in the statement that waits for it. + await client.query(receiptAppendLockSql); + let predecessor = (await client.query(receiptHeadSql)).rows[0]?.receipt_id; + if (typeof predecessor !== "string") throw new Error("receipt genesis unavailable"); + const ids: string[] = []; + for (const fields of prepared) { + const [job, ordinal, kind, claim, review, publicId, event, outcome, phase, category, recovered, recovery] = fields; + const result = await client.query(receiptInsertSql, [predecessor, claim, job, ordinal, + kind, review, publicId, event, outcome, phase, category, recovered, recovery]); + const id = result.rows[0]?.receipt_id; + if (typeof id !== "string") throw new Error("receipt append unavailable"); + ids.push(id); + predecessor = id; + } + return ids; +} + +/** Execute while holding the job and review locks, before the append tail. */ +export const stageOwnedReviewSql = ` +UPDATE jobs j +SET staged_review_id = r.id, + payload = j.payload || jsonb_build_object('recoveryReviewId', r.id) +FROM reviews r +WHERE j.id = $1::bigint AND j.status = 'running' + AND j.active_claim_receipt_id = $2::bigint AND j.locked_by = $3 + AND r.id = $4::bigint AND r.status = 'running' + AND EXISTS ( + SELECT 1 FROM job_attempt_receipts b + WHERE b.event_kind = 'review_bound' AND b.job_id = j.id + AND b.claim_receipt_id = j.active_claim_receipt_id + AND b.review_id = r.id AND b.review_public_id = r.public_id + ) +RETURNING j.id::text`; + +/** One snapshot, scalar projection, finite page; no ACK or application pool. */ +export const readAttemptReceiptPageSql = ` +WITH genesis AS MATERIALIZED ( + SELECT source_epoch, schema_version FROM job_attempt_receipts WHERE receipt_id = 0 +), anchor AS MATERIALIZED ( + SELECT receipt_id FROM job_attempt_receipts WHERE receipt_id = $1::bigint +), head AS MATERIALIZED ( + SELECT receipt_id FROM job_attempt_receipts ORDER BY receipt_id DESC LIMIT 1 +) +SELECT g.source_epoch::text, g.schema_version, + a.receipt_id::text AS anchor_id, h.receipt_id::text AS head_id, + p.receipt_id::text, p.predecessor_id::text, p.claim_receipt_id::text, + p.job_id::text, p.ordinal, p.job_kind, p.review_id::text, + p.review_public_id::text, p.event_kind, p.outcome, p.phase, p.category, + p.recovered, p.recovery, + to_char(p.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS occurred_at +FROM (SELECT 1) control +LEFT JOIN genesis g ON true LEFT JOIN anchor a ON true LEFT JOIN head h ON true +LEFT JOIN LATERAL ( + SELECT receipt_id, predecessor_id, claim_receipt_id, job_id, ordinal, + job_kind, review_id, review_public_id, event_kind, outcome, phase, + category, recovered, recovery, occurred_at + FROM job_attempt_receipts WHERE receipt_id > $1::bigint + ORDER BY receipt_id LIMIT 64 +) p ON true +ORDER BY p.receipt_id`; + + +/** The expected identity is captured at claim commit, never refreshed on completion. */ +export interface ReceiptClaim { + readonly jobId: string; + readonly receiptId: string; +} +export interface ReviewClaimLease { + id: number; + lockedBy: string; + lockedAt: Date; + receiptClaim?: ReceiptClaim; +} + +/** Bind trusted domain statements to the caller's supported Drizzle executor. */ +export function transactionReceiptClient(tx: { + execute(query: SQL): Promise<{ rows: Record[] }>; +}): ReceiptClient { + return { + async query(text, values = []) { + const parts = text.split(/\$(\d+)/g).map((part, index) => index % 2 + ? sql`${values[Number(part) - 1]}` : sql.raw(part)); + return tx.execute(sql.join(parts, sql``)); + }, + }; +} + +export async function lockReceiptClaim(client: ReceiptClient, lease: ReviewClaimLease) { + const claim = lease.receiptClaim; + if (!claim) return null; + const result = await client.query(`SELECT j.id::text, j.kind, j.attempts, + j.active_claim_receipt_id::text, j.staged_review_id::text, + (j.status = 'running' AND j.active_claim_receipt_id = $2::bigint + AND j.locked_by = $3 AND j.locked_at = $4::timestamptz) AS owned + FROM jobs j WHERE j.id = $1::bigint FOR UPDATE`, + [claim.jobId, claim.receiptId, lease.lockedBy, lease.lockedAt]); + return result.rows[0] ?? null; +} + +export function claimEvent(jobId: string, ordinal: number, claimId: string | null, + eventKind: PreparedReceipt["eventKind"], outcome: PreparedReceipt["outcome"], + reviewId: string | null = null, reviewPublicId: string | null = null, + jobKind: string = "review"): PreparedReceipt { + if (jobKind !== "review" && jobKind !== "review-feedback") throw new Error("invalid receipt job kind"); + return { jobId, ordinal, jobKind, claimId, reviewId, reviewPublicId, + eventKind, outcome, phase: null, category: null, recovered: null, recovery: null }; +} + +/** All business mutations in body precede this transaction's single append tail. */ +export async function transitionReceiptClaim( + pool: Pool, lease: ReviewClaimLease, body: (client: PoolClient) => Promise, + outcome: (result: T) => PreparedReceipt["outcome"], +): Promise { + // Queue feedback control can already own this pinned transaction. + const ownsTransaction = !("release" in pool); + const client = ownsTransaction ? await pool.connect() : pool as unknown as PoolClient; + try { + if (ownsTransaction) await client.query("BEGIN"); + const row = await lockReceiptClaim(client, lease); + if (!row?.owned) { + if (ownsTransaction) await client.query("ROLLBACK"); + return "lost"; + } + const result = await body(client); + if (result !== "lost") { + await client.query("UPDATE jobs SET active_claim_receipt_id = NULL WHERE id = $1::bigint", + [lease.receiptClaim!.jobId]); + await appendPreparedReceiptBatch(client, [claimEvent(lease.receiptClaim!.jobId, + Number(row.attempts), lease.receiptClaim!.receiptId, "queue_closed", outcome(result), + null, null, String(row.kind))]); + } + if (ownsTransaction) await client.query("COMMIT"); + return result; + } catch (error) { + if (ownsTransaction) await client.query("ROLLBACK").catch(() => undefined); + throw error; + } finally { if (ownsTransaction) client.release(); } +} + +/** Prepare terminal observations before the append lock; legacy rows stay unqualified. */ +export async function prepareReviewObservation(client: ReceiptClient, reviewId: number, + outcome: PreparedReceipt["outcome"], lease?: ReviewClaimLease): Promise { + const result = await client.query(`SELECT b.claim_receipt_id::text, b.job_id::text, + b.ordinal, b.job_kind, b.review_id::text, b.review_public_id::text + FROM job_attempt_receipts b JOIN reviews r ON r.id = b.review_id + WHERE b.event_kind = 'review_bound' AND r.id = $1::bigint + AND b.claim_receipt_id = COALESCE($2::bigint, r.origin_claim_receipt_id) + AND b.review_public_id = r.public_id + AND NOT EXISTS (SELECT 1 FROM job_attempt_receipts o + WHERE o.event_kind = 'review_observed' AND o.claim_receipt_id = b.claim_receipt_id + AND o.review_id = r.id AND o.outcome = $3)`, + [String(reviewId), lease?.receiptClaim?.receiptId ?? null, outcome]); + return result.rows.map((r) => claimEvent(String(r.job_id), Number(r.ordinal), + String(r.claim_receipt_id), "review_observed", outcome, String(r.review_id), String(r.review_public_id), + String(r.job_kind))); +} + +export interface ReceiptSourceBinding { + /** Independently admitted resource creation/incarnation, not a database self-report. */ + target: string; + epoch: string; + schema: number; +} +export interface ReceiptAdmissionState { + /** Monotonic external revision, persisted independently of the recoverable database. */ + revision: number; + binding: ReceiptSourceBinding | null; + prefix: string; + /** Confirmed report prefix, committed by the external owner before ACK. */ + reportedThrough?: string; + /** Retired incarnations stay in the external owner's durable state across code rollback. */ + retiredTargets?: readonly string[]; +} +export interface ReceiptPageProposal { + revision: number; + binding: ReceiptSourceBinding; + after: string; + through: string; + rows: Record[]; +} +const sameBinding = (a: ReceiptSourceBinding | null, b: ReceiptSourceBinding) => + a?.target === b.target && a.epoch === b.epoch && a.schema === b.schema; + +/** A recovery cut affects correlation admission, never application work or recovery. */ +export function closeReceiptAdmission(state: ReceiptAdmissionState): ReceiptAdmissionState { + return { ...state, revision: state.revision + 1, binding: null, + reportedThrough: undefined, + retiredTargets: [...new Set([...(state.retiredTargets ?? []), + ...(state.binding ? [state.binding.target] : [])])] }; +} + +/** Called by the existing admission owner under the same durable lock as cut and ACK. + * The old source prefix remains retained in that owner's source-specific state. + */ +export function admitReceiptSource(state: ReceiptAdmissionState, binding: ReceiptSourceBinding, + expectedRevision: number, sourcePrefix: string): ReceiptAdmissionState | null { + if (sameBinding(state.binding, binding) && state.revision === expectedRevision + 1) return state; + if (state.binding || state.revision !== expectedRevision || !binding.target + || !binding.epoch || binding.schema !== 1 || state.retiredTargets?.includes(binding.target) + || !/^(0|[1-9][0-9]{0,18})$/.test(sourcePrefix)) return null; + return { ...state, revision: state.revision + 1, binding: { ...binding }, prefix: sourcePrefix, + reportedThrough: undefined }; +} + +/** The admission owner serializes this update and ACK with its durable state commit. */ +export function receiptPageAdmitted(state: ReceiptAdmissionState, page: ReceiptPageProposal): boolean { + return sameBinding(state.binding, page.binding) && state.revision === page.revision + && state.prefix === page.after; +} + +/** The existing report owner commits this only after confirmed delivery under its admission lock. */ +export function recordReceiptPageReported(state: ReceiptAdmissionState, page: ReceiptPageProposal, + through: string): ReceiptAdmissionState | null { + if (!receiptPageAdmitted(state, page) || (through !== page.after + && !page.rows.some((row) => row.receipt_id === through))) return null; + return { ...state, reportedThrough: through }; +} + +/** Check admission under the owner's lock before delivery, and again before ACK. */ +export function acknowledgeReceiptPage(state: ReceiptAdmissionState, page: ReceiptPageProposal, + reportedThrough: string): ReceiptAdmissionState | null { + if (!receiptPageAdmitted(state, page) || !/^(0|[1-9][0-9]*)$/.test(reportedThrough) + || !state.reportedThrough || BigInt(reportedThrough) > BigInt(state.reportedThrough)) return null; + // Only a genuinely reported prefix of this exact page may advance the cursor. + if (reportedThrough !== page.after && !page.rows.some((r) => r.receipt_id === reportedThrough)) return null; + return { ...state, prefix: reportedThrough, reportedThrough: undefined }; +} + +/** The target witness comes from the admitted executor, outside SQL/stdout content. */ +export async function readAdmittedReceiptPage( + state: ReceiptAdmissionState, + execute: (text: string, values: unknown[]) => Promise<{ + target: string; rows: Record[]; + }>, +): Promise<{ status: "UNKNOWN" } | { status: "READY"; page: ReceiptPageProposal }> { + if (!state.binding || !/^(0|[1-9][0-9]{0,18})$/.test(state.prefix)) return { status: "UNKNOWN" }; + const snapshot = { ...state, binding: { ...state.binding } }; + try { + const response = await execute(readAttemptReceiptPageSql, [snapshot.prefix]); + const columns = ["source_epoch", "schema_version", "anchor_id", "head_id", "receipt_id", + "predecessor_id", "claim_receipt_id", "job_id", "ordinal", "job_kind", "review_id", + "review_public_id", "event_kind", "outcome", "phase", "category", "recovered", "recovery", "occurred_at"]; + if (!Array.isArray(response.rows) || response.rows.length > 64) return { status: "UNKNOWN" }; + for (const row of response.rows) { + if (!row || ![Object.prototype, null].includes(Object.getPrototypeOf(row))) return { status: "UNKNOWN" }; + const fields = Object.getOwnPropertyDescriptors(row); + if (Reflect.ownKeys(fields).length !== columns.length || columns.some((name) => { + const field = fields[name]; + if (!field || !("value" in field)) return true; + const value = field.value; + return value !== null && !(typeof value === "string" && value.length <= 64) + && !(typeof value === "number" && Number.isSafeInteger(value)) && typeof value !== "boolean"; + })) return { status: "UNKNOWN" }; + } + const control = response.rows[0]; + if (response.target !== snapshot.binding.target || !control + || control.source_epoch !== snapshot.binding.epoch || control.schema_version !== snapshot.binding.schema + || control.anchor_id !== snapshot.prefix || typeof control.head_id !== "string" + || BigInt(control.head_id) < BigInt(snapshot.prefix) + || Buffer.byteLength(JSON.stringify(response.rows)) > 65536) { + return { status: "UNKNOWN" }; + } + let predecessor = snapshot.prefix; + const rows = response.rows.filter((r) => r.receipt_id !== null); + for (const row of rows) { + if (row.source_epoch !== control.source_epoch || row.schema_version !== control.schema_version + || row.anchor_id !== control.anchor_id || row.head_id !== control.head_id + || typeof row.receipt_id !== "string" || !/^[1-9][0-9]{0,18}$/.test(row.receipt_id) + || row.predecessor_id !== predecessor || BigInt(row.receipt_id) <= BigInt(predecessor) + || BigInt(row.receipt_id) > BigInt(control.head_id)) return { status: "UNKNOWN" }; + predecessor = row.receipt_id; + } + return { status: "READY", page: { revision: snapshot.revision, binding: snapshot.binding, + after: snapshot.prefix, through: predecessor, rows } }; + } catch { return { status: "UNKNOWN" }; } +} + + +/** Called after the caller's locked administrative mutations and before its append tail. */ +export async function prepareClosedJobReceipts(client: ReceiptClient, + jobIds: readonly string[], outcome?: PreparedReceipt["outcome"]): Promise { + if (!jobIds.length) return []; + const result = await client.query(`SELECT j.id::text, j.status, c.ordinal, c.job_kind, c.receipt_id::text + FROM jobs j JOIN job_attempt_receipts c ON c.receipt_id = j.active_claim_receipt_id + WHERE j.id = ANY($1::bigint[]) AND j.status <> 'running' AND c.event_kind = 'claim' + AND NOT EXISTS (SELECT 1 FROM job_attempt_receipts x + WHERE x.claim_receipt_id = c.receipt_id AND x.event_kind = 'queue_closed') + ORDER BY j.id`, [jobIds]); + if (result.rows.length) await client.query(`UPDATE jobs SET active_claim_receipt_id = NULL + WHERE id = ANY($1::bigint[])`, [result.rows.map((r) => r.id)]); + return result.rows.map((r) => claimEvent(String(r.id), Number(r.ordinal), String(r.receipt_id), + "queue_closed", outcome ?? (r.status === "queued" ? "retry" : r.status === "failed" ? "failed" : "cancelled"), + null, null, String(r.job_kind))); +} diff --git a/src/lib/private-worker-rehearsal.ts b/src/lib/private-worker-rehearsal.ts index 380072ff..edc5870a 100644 --- a/src/lib/private-worker-rehearsal.ts +++ b/src/lib/private-worker-rehearsal.ts @@ -1,3 +1,9 @@ +import { + appendPreparedReceiptBatch, + lockReceiptClaim, + prepareClosedJobReceipts, + type ReviewClaimLease, +} from "@/lib/job-attempt-receipts"; import { randomUUID } from "node:crypto"; import type { Pool, PoolClient } from "pg"; @@ -31,6 +37,7 @@ export interface ArmPrivateWorkerRehearsalInput extends PrivateWorkerRehearsalTa export interface StagedPrivateWorkerRehearsalInput { reviewId: number; reviewJobId: number; + reviewClaim?: ReviewClaimLease; repoFullName: string; prNumber: number; headSha: string; @@ -246,6 +253,10 @@ export async function consumePrivateWorkerRehearsalAfterStaging( const client = await pool.connect(); try { await client.query("BEGIN"); + if (input.reviewClaim?.receiptClaim && !(await lockReceiptClaim(client, input.reviewClaim))?.owned) { + await client.query("ROLLBACK"); + return null; + } const request = await client.query<{ nonce: string; review_public_id: string; @@ -288,7 +299,7 @@ export async function consumePrivateWorkerRehearsalAfterStaging( FOR UPDATE OF rehearsal`, [ input.reviewId, - input.reviewJobId, + input.reviewClaim?.receiptClaim?.jobId ?? input.reviewJobId, input.repoFullName, input.prNumber, input.headSha, @@ -340,7 +351,7 @@ export async function consumePrivateWorkerRehearsalAfterStaging( AND status = 'running' AND payload->>'recoveryReviewId' = $3 AND left(locked_by, length($4) + 1) = $4 || '#'`, - [input.reviewJobId, row.nonce, String(input.reviewId), input.workerInstanceId], + [input.reviewClaim?.receiptClaim?.jobId ?? input.reviewJobId, row.nonce, String(input.reviewId), input.workerInstanceId], ); if ((marked.rowCount ?? 0) !== 1) { throw new Error("private worker rehearsal lost its exact recovery job lease"); @@ -413,6 +424,7 @@ export async function reconcilePrivateWorkerRehearsals( const client = await pool.connect(); try { await client.query("BEGIN"); + await client.query("SELECT id FROM jobs WHERE id=$1::bigint FOR UPDATE", [rehearsal.job_id]); const verified = await client.query( `UPDATE private_worker_rehearsals SET state = 'replacement_verified', @@ -477,6 +489,8 @@ export async function reconcilePrivateWorkerRehearsals( result.rehearsalsFailed += 1; } } + const events = await prepareClosedJobReceipts(client, [rehearsal.job_id], "interrupted"); + await appendPreparedReceiptBatch(client, events); await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK").catch(() => undefined); diff --git a/src/lib/queue.ts b/src/lib/queue.ts index 1f0c3c09..7998e586 100644 --- a/src/lib/queue.ts +++ b/src/lib/queue.ts @@ -3,6 +3,14 @@ import type { Pool, PoolClient } from "pg"; import { readNonNegativeIntEnv, readPositiveIntEnv } from "@/lib/env"; import { redactAndTruncate } from "@/lib/redact"; import type { ReviewTriggerContext } from "@/lib/review-trigger"; +import { + appendPreparedReceiptBatch, + claimEvent, + transitionReceiptClaim, + prepareClosedJobReceipts, + type ReceiptClaim, + type ReviewClaimLease, +} from "@/lib/job-attempt-receipts"; import type { ReviewFeedbackContext } from "@/lib/review-feedback"; /** @@ -26,6 +34,7 @@ import type { ReviewFeedbackContext } from "@/lib/review-feedback"; */ export interface ClaimedJob { + receiptClaim?: ReceiptClaim; id: number; kind: string; payload: Record; @@ -84,18 +93,19 @@ async function withFeedbackControl( pool: Pool, operation: (client: PoolClient, enabled: boolean) => Promise, ): Promise { - const client = await pool.connect(); + const ownsTransaction = !("release" in pool); + const client = ownsTransaction ? await pool.connect() : pool as unknown as PoolClient; try { - await client.query("BEGIN"); + if (ownsTransaction) await client.query("BEGIN"); const enabled = await lockFeedbackControl(client); const result = await operation(client, enabled); - await client.query("COMMIT"); + if (ownsTransaction) await client.query("COMMIT"); return result; } catch (error) { - await client.query("ROLLBACK").catch(() => undefined); + if (ownsTransaction) await client.query("ROLLBACK").catch(() => undefined); throw error; } finally { - client.release(); + if (ownsTransaction) client.release(); } } @@ -191,6 +201,7 @@ export function hostedProviderKeyLifecycleJobPayload( } export interface ExternalSideEffectLease { + receiptClaim?: ReceiptClaim; id: number; lockedBy: string; lockedAt: Date; @@ -208,8 +219,9 @@ export async function externalSideEffectLeaseActive( AND status = 'running' AND locked_by = $2 AND locked_at = $3 + AND ($4::bigint IS NULL OR active_claim_receipt_id = $4::bigint) LIMIT 1`, - [lease.id, lease.lockedBy, lease.lockedAt], + [lease.receiptClaim?.jobId ?? lease.id, lease.lockedBy, lease.lockedAt, lease.receiptClaim?.receiptId ?? null], ); return (result.rowCount ?? 0) === 1; } @@ -226,62 +238,87 @@ export async function cancelPullRequestPublication( prNumber: number; }, ): Promise { - const result = await pool.query( - `WITH matching_jobs AS MATERIALIZED ( - SELECT id - FROM jobs - WHERE kind IN ('review', 'review-feedback', 'respond', 'respond-failure-comment', 'webhook-comment') - AND status IN ('queued', 'running') - AND payload->>'installationId' = $1::text - AND payload->>'sourceInstallationId' = $2::text - AND payload->>'sourceOrgId' = $3::text - AND payload->>'githubRepoId' = $4::text - AND lower(payload->>'repoFullName') = lower($5) - AND COALESCE((payload->>'prNumber')::integer, (payload->>'number')::integer) = $6 - AND COALESCE((payload->>'isPr')::boolean, kind IN ('review', 'review-feedback')) - ), cancelled_deliveries AS ( - UPDATE respond_deliveries delivery - SET state = 'cancelled', - publication_lease_id = NULL, - publication_lease_expires_at = NULL, - delivery_lease_expires_at = NULL, - cancelled_at = now(), - updated_at = now() - WHERE delivery.source_github_installation_id = $1::bigint - AND delivery.source_installation_id = $2::bigint - AND delivery.source_org_id = $3::bigint - AND delivery.source_github_repo_id = $4::bigint - AND lower(delivery.repo_full_name) = lower($5) - AND delivery.issue_number = $6 - AND delivery.is_pr - AND delivery.state IN ('prepared', 'delivering') - RETURNING delivery.job_id - ), delivery_jobs AS ( - SELECT job.id - FROM jobs job - JOIN cancelled_deliveries delivery - ON job.kind = 'respond-delivery' - AND job.payload->>'respondJobId' = delivery.job_id::text - WHERE job.status IN ('queued', 'running') - ), terminal_ids AS ( - SELECT id FROM matching_jobs - UNION SELECT id FROM delivery_jobs - ) - UPDATE jobs job - SET status = 'done', locked_at = NULL, locked_by = NULL, - last_error = 'pull request closed' - WHERE job.id IN (SELECT id FROM terminal_ids) - AND job.status IN ('queued', 'running')`, - [ - String(input.installationId), - String(input.sourceInstallationId), - String(input.sourceOrgId), - String(input.githubRepoId), - input.repoFullName, - input.prNumber, - ], - ); - return result.rowCount ?? 0; + const run = async (client: Pick, qualified: boolean) => { + if (qualified) await client.query(`SELECT id FROM jobs + WHERE status IN ('queued', 'running') AND kind IN ('review', 'review-feedback') + AND payload->>'installationId' = $1 AND payload->>'sourceInstallationId' = $2 + AND payload->>'sourceOrgId' = $3 AND payload->>'githubRepoId' = $4 + AND lower(payload->>'repoFullName') = lower($5) + AND (payload->>'prNumber')::integer = $6 ORDER BY id FOR UPDATE`, + [String(input.installationId), String(input.sourceInstallationId), String(input.sourceOrgId), + String(input.githubRepoId), input.repoFullName, input.prNumber]); + const result = await client.query( + `WITH matching_jobs AS MATERIALIZED ( + SELECT id + FROM jobs + WHERE kind IN ('review', 'review-feedback', 'respond', 'respond-failure-comment', 'webhook-comment') + AND status IN ('queued', 'running') + AND payload->>'installationId' = $1::text + AND payload->>'sourceInstallationId' = $2::text + AND payload->>'sourceOrgId' = $3::text + AND payload->>'githubRepoId' = $4::text + AND lower(payload->>'repoFullName') = lower($5) + AND COALESCE((payload->>'prNumber')::integer, (payload->>'number')::integer) = $6 + AND COALESCE((payload->>'isPr')::boolean, kind IN ('review', 'review-feedback')) + ), cancelled_deliveries AS ( + UPDATE respond_deliveries delivery + SET state = 'cancelled', + publication_lease_id = NULL, + publication_lease_expires_at = NULL, + delivery_lease_expires_at = NULL, + cancelled_at = now(), + updated_at = now() + WHERE delivery.source_github_installation_id = $1::bigint + AND delivery.source_installation_id = $2::bigint + AND delivery.source_org_id = $3::bigint + AND delivery.source_github_repo_id = $4::bigint + AND lower(delivery.repo_full_name) = lower($5) + AND delivery.issue_number = $6 + AND delivery.is_pr + AND delivery.state IN ('prepared', 'delivering') + RETURNING delivery.job_id + ), delivery_jobs AS ( + SELECT job.id + FROM jobs job + JOIN cancelled_deliveries delivery + ON job.kind = 'respond-delivery' + AND job.payload->>'respondJobId' = delivery.job_id::text + WHERE job.status IN ('queued', 'running') + ), terminal_ids AS ( + SELECT id FROM matching_jobs + UNION SELECT id FROM delivery_jobs + ) + UPDATE jobs job + SET status = 'done', locked_at = NULL, locked_by = NULL, + last_error = 'pull request closed' + WHERE job.id IN (SELECT id FROM terminal_ids) + AND job.status IN ('queued', 'running') RETURNING job.id::text`, + [ + String(input.installationId), + String(input.sourceInstallationId), + String(input.sourceOrgId), + String(input.githubRepoId), + input.repoFullName, + input.prNumber, + ], + ); + if (qualified) { + const events = await prepareClosedJobReceipts(client, result.rows.map((r) => String(r.id)), "cancelled"); + await appendPreparedReceiptBatch(client, events); + } + return result.rowCount ?? 0; + }; + if (!("connect" in pool) || !("totalCount" in pool)) return run(pool, false); + const client = await (pool as Pool).connect(); + try { + await client.query("BEGIN"); + const count = await run(client, true); + await client.query("COMMIT"); + return count; + } catch (error) { + await client.query("ROLLBACK").catch(() => undefined); + throw error; + } finally { client.release(); } } export interface RespondDeliveryJobPayload extends Record { @@ -1087,6 +1124,12 @@ export async function claimNextJob( ) { throw new Error("claimed job returned incomplete candidate data"); } + const receipts = (row.kind === "review" || row.kind === FEEDBACK_REVIEW_JOB_KIND) && (await client.query( + "SELECT to_regclass('job_attempt_receipts') IS NOT NULL AS available", + )).rows[0]?.available === true; + if (receipts) { + await client.query("UPDATE jobs SET active_claim_receipt_id = NULL WHERE id = $1", [row.id]); + } const claimed = await client.query<{ locked_at: Date }>( `UPDATE jobs SET status = 'running', attempts = attempts + 1, @@ -1097,6 +1140,13 @@ export async function claimNextJob( ); const lockedAt = claimed.rows[0]?.locked_at; if (!lockedAt) throw new Error("claimed job returned no lock timestamp"); + let receiptClaim: ReceiptClaim | undefined; + if (receipts) { + const [receiptId] = await appendPreparedReceiptBatch(client, + [claimEvent(row.id, row.attempts + 1, null, "claim", "started", null, null, row.kind)]); + await client.query("UPDATE jobs SET active_claim_receipt_id = $2::bigint WHERE id = $1::bigint", [row.id, receiptId]); + receiptClaim = Object.freeze({ jobId: row.id, receiptId: receiptId! }); + } await client.query("COMMIT"); return { status: "claimed", @@ -1109,6 +1159,7 @@ export async function claimNextJob( createdAt: row.created_at, lockedAt, lockedBy: workerId, + ...(receiptClaim ? { receiptClaim } : {}), }, }; } catch (err) { @@ -1119,6 +1170,9 @@ export async function claimNextJob( } } +type TransitionJob = Pick & + Partial> & { id: number | string }; + /** * Claim one job, collapsing "empty queue" and "deferred by the cap" into null. * @@ -1174,7 +1228,7 @@ export function nextClaimPollDelay( */ export async function completeJob( pool: Pool, - job: Pick, + job: TransitionJob, ): Promise<"done" | "coalesced" | "lost"> { return withFeedbackControl(pool, async (client) => { // Enqueue takes these advisory locks before its job-row lock. The insert @@ -1209,6 +1263,9 @@ export async function completeJob( ]); } } + if (job.receiptClaim) return transitionReceiptClaim(client as unknown as Pool, job as ReviewClaimLease, + (locked) => completeJob(locked as unknown as Pool, { ...job, receiptClaim: undefined, id: job.receiptClaim!.jobId }), + (result) => result === "coalesced" ? "coalesced" : "succeeded"); const result = await client.query<{ outcome: "done" | "coalesced" | "lost" }>( `WITH transitioned AS ( UPDATE jobs @@ -1236,10 +1293,18 @@ export async function completeJob( export async function continueClaimedJob( pool: Pool, - job: Pick, + job: TransitionJob, payload: Record, options: { runAfter?: Date } = {}, ): Promise { + if (job.receiptClaim) { + const result = await transitionReceiptClaim(pool, job as ReviewClaimLease, async (client) => { + await continueClaimedJob(client as unknown as Pool, { ...job, receiptClaim: undefined, id: job.receiptClaim!.jobId }, payload, options); + return "continued"; + }, () => "continued"); + if (result === "lost") throw new Error("job continuation lost its lease"); + return; + } const result = await pool.query( `UPDATE jobs SET payload = $3, status = 'queued', attempts = 0, @@ -1259,14 +1324,34 @@ export async function requeueJobsOwnedBy( lockedByPrefix: string, reason: string, kinds: readonly string[], - jobIds: readonly number[], + jobIds: readonly (number | string)[], + expectedClaims?: readonly ReviewClaimLease[], ): Promise { + if (expectedClaims) { + let count = 0; + for (const lease of expectedClaims) { + if (!jobIds.includes(lease.id) || !lease.lockedBy.startsWith(lockedByPrefix)) continue; + if (!lease.receiptClaim) { + count += await requeueJobsOwnedBy(pool, lockedByPrefix, reason, kinds, [lease.id]); + continue; + } + const result = await withFeedbackControl(pool, (client) => transitionReceiptClaim(client as unknown as Pool, lease, async (client) => { + if (!await requeueJobsOwnedBy(client as unknown as Pool, lockedByPrefix, reason, kinds, + [lease.receiptClaim!.jobId])) return "lost"; + const row = (await client.query("SELECT status FROM jobs WHERE id = $1::bigint", + [lease.receiptClaim!.jobId])).rows[0]; + return row?.status === "done" ? "coalesced" : "requeued"; + }, (result) => result === "coalesced" ? "coalesced" : "interrupted")); + if (result !== "lost") count += 1; + } + return count; + } if (!lockedByPrefix) throw new Error("requeueJobsOwnedBy requires a lock-owner prefix"); const allowedKinds = [...new Set(kinds.filter(Boolean))]; if (allowedKinds.length === 0) { throw new Error("requeueJobsOwnedBy requires at least one safe job kind"); } - const ownedJobIds = [...new Set(jobIds.filter((id) => Number.isSafeInteger(id) && id > 0))]; + const ownedJobIds = [...new Set(jobIds.filter((id) => typeof id === "string" ? /^[1-9][0-9]*$/.test(id) : Number.isSafeInteger(id) && id > 0))]; if (ownedJobIds.length === 0) return 0; const redactedReason = redactAndTruncate(reason, 2000); return withFeedbackControl(pool, async (client, enabled) => { @@ -1340,7 +1425,7 @@ export function backoffMs(attempts: number): number { */ export async function failJob( pool: Pool, - job: Pick, + job: TransitionJob & Pick, error: string, opts: { permanent?: boolean; @@ -1351,6 +1436,9 @@ export async function failJob( }; } = {}, ): Promise<"retried" | "failed" | "coalesced" | "lost"> { + if (job.receiptClaim) return withFeedbackControl(pool, (client) => transitionReceiptClaim(client as unknown as Pool, job as ReviewClaimLease, + (client) => failJob(client as unknown as Pool, { ...job, receiptClaim: undefined, id: job.receiptClaim!.jobId }, error, opts), + (result) => result === "retried" ? "retry" : result === "coalesced" ? "coalesced" : "failed")); const redactedError = redactAndTruncate(error, 2000); return withFeedbackControl(pool, async (client, enabled) => { if (!opts.permanent && job.attempts < job.maxAttempts) { @@ -1497,12 +1585,15 @@ export const PUBLICATION_RECONCILIATION_BUDGET_MS = readPositiveIntEnv( */ export async function retryJobIndefinitely( pool: Pool, - job: Pick, + job: TransitionJob & Pick, error: string, budget: | number | { budgetMs?: number; startedAt?: Date } = PUBLICATION_RECONCILIATION_BUDGET_MS, ): Promise<"retried" | "lost" | "exhausted"> { + if (job.receiptClaim) return transitionReceiptClaim(pool, job as ReviewClaimLease, + (client) => retryJobIndefinitely(client as unknown as Pool, { ...job, receiptClaim: undefined, id: job.receiptClaim!.jobId }, error, budget), + (result) => result === "exhausted" ? "failed" : "retry"); const redactedError = redactAndTruncate(error, 2000); const budgetMs = typeof budget === "number" ? budget diff --git a/src/lib/release-job-rollout.ts b/src/lib/release-job-rollout.ts index 987c490e..0228e86c 100644 --- a/src/lib/release-job-rollout.ts +++ b/src/lib/release-job-rollout.ts @@ -1,5 +1,11 @@ import { createHash, randomUUID } from "node:crypto"; +import { + appendPreparedReceiptBatch, + claimEvent, + lockReceiptClaim, + type ReviewClaimLease, +} from "@/lib/job-attempt-receipts"; import type { Pool, PoolClient } from "pg"; import type { Database } from "@/lib/db"; @@ -1640,7 +1646,7 @@ export async function restoreAllManagedReleasePreparations( /** Atomically park a claimed hosted review until a verified managed release activates. */ export async function deferHostedReviewForRelease( pool: Pool, - job: { id: number; lockedBy: string; kind?: string }, + job: { id: number; lockedBy: string; kind?: string; lockedAt?: Date; receiptClaim?: ReviewClaimLease["receiptClaim"] }, releaseSha: string, ): Promise<"deferred" | "released"> { return deferHostedJobForRelease(pool, job, releaseSha, job.kind === "review-feedback" ? "review-feedback" : "review"); @@ -1662,7 +1668,7 @@ export async function deferHostedProviderKeyLifecycleForRelease( async function deferHostedJobForRelease( pool: Pool, - job: { id: number; lockedBy: string }, + job: { id: number; lockedBy: string; lockedAt?: Date; receiptClaim?: ReviewClaimLease["receiptClaim"] }, releaseSha: string, kind: "review" | "review-feedback" | typeof HOSTED_PROVIDER_KEY_LIFECYCLE_JOB_KIND, ): Promise<"deferred" | "released"> { @@ -1674,6 +1680,11 @@ async function deferHostedJobForRelease( "SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [HOSTED_INFERENCE_LOCK], ); + const claim = job.receiptClaim ? await lockReceiptClaim(client, job as ReviewClaimLease) : null; + if (job.receiptClaim && !claim?.owned) { + await client.query("ROLLBACK"); + throw new Error("hosted review release deferral lost its queue claim"); + } const active = await client.query<{ active: boolean }>( `SELECT EXISTS ( SELECT 1 FROM deployment_capabilities WHERE name = $1 @@ -1695,11 +1706,17 @@ async function deferHostedJobForRelease( END WHERE id = $1 AND kind = $5 AND status = 'running' AND locked_by = $2`, - [job.id, job.lockedBy, activated, normalized, kind], + [job.receiptClaim?.jobId ?? job.id, job.lockedBy, activated, normalized, kind], ); if ((updated.rowCount ?? 0) !== 1) { throw new Error("hosted review release deferral lost its queue claim"); } + if (job.receiptClaim) { + await client.query("UPDATE jobs SET active_claim_receipt_id = NULL WHERE id = $1::bigint", [job.receiptClaim.jobId]); + await appendPreparedReceiptBatch(client, [claimEvent(job.receiptClaim.jobId, + Number(claim!.attempts), job.receiptClaim.receiptId, "queue_closed", "deferred", + null, null, String(claim!.kind))]); + } await client.query("COMMIT"); return activated ? "released" : "deferred"; } catch (error) { diff --git a/src/lib/review-completion.ts b/src/lib/review-completion.ts index 8e6b52e4..d4aebd98 100644 --- a/src/lib/review-completion.ts +++ b/src/lib/review-completion.ts @@ -1,6 +1,10 @@ import { and, eq, isNotNull, sql } from "drizzle-orm"; import type { Database } from "@/lib/db"; +import { + appendPreparedReceiptBatch, claimEvent, lockReceiptClaim, prepareReviewObservation, + transactionReceiptClient, type ReviewClaimLease, +} from "@/lib/job-attempt-receipts"; import { schema } from "@/lib/db"; import { computeEffectiveGate, @@ -10,12 +14,14 @@ import { import type { ReviewConfigProvenance } from "@/lib/github/contents"; import { lockReviewDecisionScopeById } from "@/lib/finding-approvals"; import { lockOrganizationGateMode } from "@/lib/gate-mode"; +import { reconcileHostedReviewSpendFromReceipt } from "@/lib/hosted-usage-reservations"; import { persistPublicationReceipt, type PublicationReceipt, } from "@/lib/publication-receipt"; interface ReviewCompletionAccountingInput { + reviewClaim?: ReviewClaimLease; reviewId: number; usage: Array<{ orgId: number | null; @@ -57,6 +63,9 @@ export interface StagedReviewCompletionInput extends Pick< | "publicationReceipt" > { reviewJobId?: number; + reviewClaim?: ReviewClaimLease; + /** Trusted CLI spend settles atomically when claim or terminal state rejects completion. */ + hostedSpend?: Parameters[1]; } function requireEnvelopeGateTruth( @@ -91,7 +100,8 @@ async function persistReviewCompletionAccounting( if (input.hostedUsageReservationId) { const reservation = ( await db - .select({ reservedMicros: schema.hostedUsageReservations.reservedMicros }) + .select({ reservedMicros: schema.hostedUsageReservations.reservedMicros, + status: schema.hostedUsageReservations.status, reviewId: schema.hostedUsageReservations.reviewId }) .from(schema.hostedUsageReservations) .where(eq(schema.hostedUsageReservations.id, input.hostedUsageReservationId)) .limit(1) @@ -138,10 +148,14 @@ async function persistReviewCompletionAccounting( ) .returning({ id: schema.hostedUsageReservations.id }); if (reconciled.length !== 1) { - throw new Error("hosted usage reservation is not active"); + if (reservation.status !== "reconciled" || reservation.reviewId !== input.reviewId) { + throw new Error("hosted usage reservation is not active"); + } + // A late producer can settle this same review before its recovery owner finalizes it. + persistedUsageRows.length = 0; } } - await db.insert(schema.usageEvents).values(persistedUsageRows); + if (persistedUsageRows.length > 0) await db.insert(schema.usageEvents).values(persistedUsageRows); if (input.queueGateStateSync !== false) { await db.insert(schema.jobs).values({ kind: "gate-state-sync", @@ -164,9 +178,36 @@ export async function stageReviewCompletionCandidate( db: Database, input: StagedReviewCompletionInput, orgId: number | null, -): Promise { +): Promise { return db.transaction(async (tx) => { await lockReviewDecisionScopeById(tx, input.reviewId); + const client = transactionReceiptClient(tx); + const lease = input.reviewClaim; + if (lease?.receiptClaim && input.reviewJobId !== undefined + && (Number.isSafeInteger(input.reviewJobId) + ? String(input.reviewJobId) !== lease.receiptClaim.jobId : input.reviewJobId !== lease.id)) { + throw new Error("staging job identity mismatch"); + } + const job = lease?.receiptClaim ? await lockReceiptClaim(client, lease) : null; + let alreadyStaged = false; + if (lease?.receiptClaim) { + const binding = await client.query(`SELECT EXISTS (SELECT 1 FROM job_attempt_receipts o + WHERE o.claim_receipt_id = b.claim_receipt_id AND o.review_id = r.id + AND o.event_kind = 'review_observed' AND o.outcome = 'staged') AS staged, + r.envelope = $4::jsonb AS same_envelope, + (SELECT p.receipt_id FROM review_publication_receipts p WHERE p.review_id = r.id) AS publication_id + FROM job_attempt_receipts b JOIN reviews r + ON r.id = b.review_id AND r.public_id = b.review_public_id + WHERE b.claim_receipt_id = $1::bigint AND b.job_id = $2::bigint + AND b.event_kind = 'review_bound' AND r.id = $3::bigint`, + [lease.receiptClaim.receiptId, lease.receiptClaim.jobId, String(input.reviewId), JSON.stringify(input.envelope)]); + if (binding.rows.length !== 1) throw new Error("staging review claim binding is missing"); + alreadyStaged = binding.rows[0]!.staged === true; + if (alreadyStaged && (binding.rows[0]!.same_envelope !== true + || (input.publicationReceipt && binding.rows[0]!.publication_id !== input.publicationReceipt.receiptId))) { + throw new Error("staged review evidence changed"); + } + } const gateTruth = requireEnvelopeGateTruth( input.envelope, input.gateFailing, @@ -193,7 +234,19 @@ export async function stageReviewCompletionCandidate( eq(schema.reviews.status, "running"), ), ) - .returning({ id: schema.reviews.id }); + .returning({ id: schema.reviews.id, repositoryId: schema.reviews.repositoryId, + triggerSource: schema.reviews.triggerSource }); + if (input.hostedSpend && (rows.length === 0 || (lease?.receiptClaim && !job?.owned))) { + const review = rows[0] ?? (await tx + .select({ repositoryId: schema.reviews.repositoryId, triggerSource: schema.reviews.triggerSource }) + .from(schema.reviews).where(eq(schema.reviews.id, input.reviewId)).limit(1))[0]; + if (!review || input.hostedSpend.reviewId !== input.reviewId + || input.hostedSpend.repositoryId !== review.repositoryId + || input.hostedSpend.triggerSource !== review.triggerSource) { + throw new Error("staging hosted spend identity mismatch"); + } + await reconcileHostedReviewSpendFromReceipt(tx as Database, input.hostedSpend); + } if (rows.length === 0) { return { staged: false, @@ -203,12 +256,12 @@ export async function stageReviewCompletionCandidate( }; } - await persistPublicationReceipt(tx as Database, { + if (!alreadyStaged) await persistPublicationReceipt(tx as Database, { reviewId: input.reviewId, envelope: gateTruth.envelope, receipt: input.publicationReceipt, }); - if (input.reviewJobId !== undefined) { + if (!lease?.receiptClaim && input.reviewJobId !== undefined) { await tx .update(schema.jobs) .set({ @@ -219,7 +272,28 @@ export async function stageReviewCompletionCandidate( .where(eq(schema.jobs.id, input.reviewJobId)); } + if (lease?.receiptClaim) { + if (job?.owned) { + const updated = await client.query(`UPDATE jobs SET staged_review_id = $3::bigint, + payload = payload || jsonb_build_object('recoveryReviewId', $3::bigint) + WHERE id = $1::bigint AND status = 'running' AND active_claim_receipt_id = $2::bigint + AND locked_by = $4 AND locked_at = $5::timestamptz RETURNING id`, + [lease.receiptClaim.jobId, lease.receiptClaim.receiptId, String(input.reviewId), lease.lockedBy, lease.lockedAt]); + if (updated.rows.length !== 1) throw new Error("staging lost its locked claim"); + } + const events = await prepareReviewObservation(client, input.reviewId, "staged", lease); + if (events.length) { + for (const incident of gateTruth.envelope.modelIncidents ?? []) { + events.push({ ...claimEvent(lease.receiptClaim.jobId, events[0]!.ordinal, + lease.receiptClaim.receiptId, "incident", "observed", String(input.reviewId), + events[0]!.reviewPublicId, events[0]!.jobKind), phase: incident.phase, category: incident.category, + recovered: incident.recovered, recovery: incident.recovery ?? null }); + } + } + await appendPreparedReceiptBatch(client, events); + } return { + claimOwned: lease?.receiptClaim ? job?.owned === true : null, staged: true, completed: false, gateEnabled, @@ -236,6 +310,24 @@ export async function finalizeStagedReviewCompletionWithGateMode( ): Promise { return db.transaction(async (tx) => { await lockReviewDecisionScopeById(tx, input.reviewId); + const client = transactionReceiptClient(tx); + let qualified = false; + if (input.reviewClaim?.receiptClaim && !(await lockReceiptClaim(client, input.reviewClaim))?.owned) { + return { completed: false, gateEnabled: false, gateFailing: false }; + } + if (input.reviewClaim?.receiptClaim) { + const binding = await client.query(`SELECT r.origin_claim_receipt_id::text, + EXISTS (SELECT 1 FROM job_attempt_receipts b WHERE b.review_id = r.id + AND b.review_public_id = r.public_id AND b.event_kind = 'review_bound' + AND b.claim_receipt_id = $1::bigint AND b.job_id = $2::bigint) AS bound + FROM reviews r WHERE r.id = $3::bigint`, + [input.reviewClaim.receiptClaim.receiptId, input.reviewClaim.receiptClaim.jobId, String(input.reviewId)]); + const row = binding.rows[0]; + if (row?.origin_claim_receipt_id !== null) { + if (!row?.bound) throw new Error("completion review claim binding is missing"); + qualified = true; + } + } const gateEnabled = orgId === null ? false : await lockOrganizationGateMode(tx, orgId); const staged = ( @@ -287,6 +379,10 @@ export async function finalizeStagedReviewCompletionWithGateMode( } await persistReviewCompletionAccounting(tx as Database, input, rows[0]!); + if (qualified) { + const events = await prepareReviewObservation(client, input.reviewId, "completed", input.reviewClaim); + await appendPreparedReceiptBatch(client, events); + } return { completed: true, gateEnabled, gateFailing: effectiveGateFailing }; }); } @@ -383,3 +479,52 @@ export async function persistReviewCompletionWithGateMode( return { completed: true, gateEnabled, gateFailing: effectiveGateFailing }; }); } + + +/** Create a review and its immutable claim binding in the owning transaction. */ +export async function insertReviewForClaim(db: Database, + values: Omit, lease?: ReviewClaimLease) { + if (!lease?.receiptClaim) { + return (await db.insert(schema.reviews).values(values) + .returning({ id: schema.reviews.id, publicId: schema.reviews.publicId }))[0]; + } + return db.transaction(async (tx) => { + const client = transactionReceiptClient(tx); + const job = await lockReceiptClaim(client, lease); + if (!job?.owned) return undefined; + const [review] = await tx.insert(schema.reviews) + .values({ ...values, originClaimReceiptId: lease.receiptClaim!.receiptId }) + .returning({ id: schema.reviews.id, publicId: schema.reviews.publicId, receiptReviewId: sql`${schema.reviews.id}::text` }); + if (!review) throw new Error("review insert returned no row"); + await appendPreparedReceiptBatch(client, [claimEvent(lease.receiptClaim!.jobId, + Number(job.attempts), lease.receiptClaim!.receiptId, "review_bound", "bound", + review.receiptReviewId, review.publicId, String(job.kind))]); + return review; + }); +} + +/** Only the locked job's genuine staged pointer can authorize a recovery binding. */ +export async function bindStagedReviewClaim(db: Database, reviewId: number, lease: ReviewClaimLease) { + if (!lease.receiptClaim) return false; + return db.transaction(async (tx) => { + const client = transactionReceiptClient(tx); + const job = await lockReceiptClaim(client, lease); + if (!job?.owned || job.staged_review_id !== String(reviewId)) return false; + const result = await client.query(`SELECT r.public_id::text, r.origin_claim_receipt_id::text, + c.job_id::text FROM reviews r JOIN job_attempt_receipts c + ON c.receipt_id = r.origin_claim_receipt_id AND c.event_kind = 'claim' + WHERE r.id = $1::bigint AND c.job_id = $2::bigint FOR UPDATE OF r`, + [String(reviewId), lease.receiptClaim!.jobId]); + const review = result.rows[0]; + if (!review) return false; + const existing = await client.query(`SELECT 1 FROM job_attempt_receipts + WHERE event_kind = 'review_bound' AND claim_receipt_id = $1::bigint AND review_id = $2::bigint`, + [lease.receiptClaim!.receiptId, String(reviewId)]); + if (!existing.rows.length) { + await appendPreparedReceiptBatch(client, [claimEvent(lease.receiptClaim!.jobId, + Number(job.attempts), lease.receiptClaim!.receiptId, "review_bound", "bound", + String(reviewId), String(review.public_id), String(job.kind))]); + } + return true; + }); +} diff --git a/src/worker/index.ts b/src/worker/index.ts index b30b3a19..7f7ae911 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -13,6 +13,7 @@ import { runWebhookRedeliveryPass } from "@/lib/github/webhook-redelivery"; import { claimNextJob, type ClaimOutcome, + type ClaimedJob, enqueueGateEnforcementSweepOnce, FEEDBACK_REVIEW_JOB_KIND, nextClaimPollDelay, @@ -78,6 +79,7 @@ let wakeHeartbeat: (() => void) | undefined; let activeWebhookRedeliveryController: AbortController | undefined; const activeRuns = new Set>(); const activeControllers = new Map(); +const activeClaimLeases = new Map(); const requeueableReviewIds = new Set(); let pendingClaims = 0; @@ -144,6 +146,7 @@ async function claimLoop(slot: number): Promise { "worker stopped before starting the claim", [outcome.job.kind], [outcome.job.id], + [outcome.job], ); return; } @@ -178,11 +181,13 @@ async function claimLoop(slot: number): Promise { ); activeRuns.add(run); activeControllers.set(job.id, controller); + activeClaimLeases.set(job.receiptClaim?.jobId ?? String(job.id), job); try { await run; } finally { activeRuns.delete(run); activeControllers.delete(job.id); + activeClaimLeases.delete(job.receiptClaim?.jobId ?? String(job.id)); requeueableReviewIds.delete(job.id); } } @@ -224,6 +229,7 @@ async function shutdown(signal: string): Promise { "worker shutdown interrupted the claim", ["review", FEEDBACK_REVIEW_JOB_KIND], activeReviewJobIds, + [...activeClaimLeases.values()], ).catch((error) => { console.error(`failed to requeue shutdown claims: ${redactSecrets(error)}`); return 0; diff --git a/src/worker/review.ts b/src/worker/review.ts index 96ff9ac3..421abfe4 100644 --- a/src/worker/review.ts +++ b/src/worker/review.ts @@ -2,8 +2,13 @@ import { spawn } from "node:child_process"; import { mkdir, rm, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; -import { and, desc, eq, inArray, isNotNull, ne, sql } from "drizzle-orm"; +import { and, desc, eq, inArray, isNotNull, isNull, ne, sql } from "drizzle-orm"; +import { + appendPreparedReceiptBatch, + prepareReviewObservation, + transactionReceiptClient, +} from "@/lib/job-attempt-receipts"; import { validateApiBase } from "@/lib/api-base"; import { calculateUsageCostMicrosForModel } from "@/lib/billing-credits"; import { @@ -26,6 +31,7 @@ import { import { classifyOperationalModelIncidents, ingestEnvelope, + envelopeSchema, isEnvelopeOperationallyUnavailable, isReviewCoverageCapacityFailure, type Envelope, @@ -100,6 +106,8 @@ import { completeReviewPublicationLifecycle, finalizeStagedReviewCompletionWithGateMode, stageReviewCompletionCandidate, + insertReviewForClaim, + bindStagedReviewClaim, type ReviewCompletionInput, } from "@/lib/review-completion"; import { @@ -825,11 +833,13 @@ export async function resumeStagedReviewCompletion(input: { installation: { id: number; orgId: number | null; orgSlug: string | null }; repository: { id: number; githubRepoId: number; fullName: string }; signal?: AbortSignal; + lease?: ExternalSideEffectLease; }): Promise { const { db, pool, payload, installation, repository, signal } = input; const trigger = normalizeReviewTriggerContext(payload.trigger); const selection = { id: schema.reviews.id, + originClaimReceiptId: schema.reviews.originClaimReceiptId, publicId: schema.reviews.publicId, status: schema.reviews.status, repositoryId: schema.reviews.repositoryId, @@ -877,6 +887,7 @@ export async function resumeStagedReviewCompletion(input: { eq(schema.reviews.triggerContext, trigger), eq(schema.reviews.status, "running"), isNotNull(schema.reviews.envelope), + isNull(schema.reviews.originClaimReceiptId), ), ) .orderBy(desc(schema.reviews.id)) @@ -901,115 +912,140 @@ export async function resumeStagedReviewCompletion(input: { ) { throw new PermanentJobError("review publication recovery identity changed"); } - if ( - stagedReview.status !== "running" && - stagedReview.status !== "completed" - ) { - console.warn( - `review publication recovery ${stagedReview.id} is already ${stagedReview.status}`, - ); - return true; - } - if ( - !stagedReview.envelope || - stagedReview.advisoryCheckRunId == null || - stagedReview.gateCheckRunId == null - ) { - throw new PermanentJobError("review publication recovery state is incomplete"); + if (input.lease?.receiptClaim) { + // Heuristic legacy recovery remains available but never creates a typed relationship. + if (stagedReview.originClaimReceiptId !== null) { + if (!await bindStagedReviewClaim(db, stagedReview.id, input.lease)) { + throw new ReviewPublicationReconciliationError("review recovery lost its claim binding"); + } + } } + const parsed = envelopeSchema.safeParse(stagedReview.envelope); + const reservation = parsed.success ? (await db + .select({ id: schema.hostedUsageReservations.id }) + .from(schema.hostedUsageReservations) + .where(eq(schema.hostedUsageReservations.reviewId, stagedReview.id)).limit(1))[0] : undefined; + let recoveryToken: string | undefined; + try { + if (input.lease && !await externalSideEffectLeaseActive(pool, input.lease)) return true; + if ( + stagedReview.status !== "running" && + stagedReview.status !== "completed" + ) { + console.warn( + `review publication recovery ${stagedReview.id} is already ${stagedReview.status}`, + ); + return true; + } + if ( + !stagedReview.envelope || + stagedReview.advisoryCheckRunId == null || + stagedReview.gateCheckRunId == null + ) { + throw new PermanentJobError("review publication recovery state is incomplete"); + } - const token = await translateWorkerAbort( - getInstallationToken(payload.installationId, signal), - signal, - ); - const currentRepository = await translateWorkerAbort( - fetchRepositorySummary(token, payload.repoFullName, signal), - signal, - ); - if ( - currentRepository.id !== payload.githubRepoId || - currentRepository.full_name !== payload.repoFullName - ) { - throw new PermanentJobError( - "review publication recovery repository identity changed", + const token = recoveryToken = await translateWorkerAbort( + getInstallationToken(payload.installationId, signal), + signal, ); - } - const operationallyUnavailable = isEnvelopeOperationallyUnavailable( - stagedReview.envelope, - ); - const detailsUrl = reviewDetailsUrl( - stagedReview.publicId, - installation.orgSlug, - ); - const reconciliationStartedAt = - stagedReview.finishedAt ?? stagedReview.publicationStagedAt ?? new Date(); - try { - await verifyCompletedCheckRun( - token, - payload.repoFullName, - { - id: stagedReview.advisoryCheckRunId, - name: ADVISORY_CHECK_NAME, - externalId: checkRunExternalId(stagedReview.publicId, "review"), - headSha: payload.headSha, - conclusion: operationallyUnavailable ? "failure" : "success", - requireOutput: true, - detailsUrl, - }, + const currentRepository = await translateWorkerAbort( + fetchRepositorySummary(token, payload.repoFullName, signal), signal, ); - if (stagedReview.status === "running") { - const reservation = ( - await db - .select({ id: schema.hostedUsageReservations.id }) - .from(schema.hostedUsageReservations) - .where(eq(schema.hostedUsageReservations.reviewId, stagedReview.id)) - .limit(1) - )[0]; - const completion = await finalizeStagedReviewCompletionWithGateMode( - db, + if ( + currentRepository.id !== payload.githubRepoId || + currentRepository.full_name !== payload.repoFullName + ) { + throw new PermanentJobError( + "review publication recovery repository identity changed", + ); + } + const operationallyUnavailable = isEnvelopeOperationallyUnavailable( + stagedReview.envelope, + ); + const detailsUrl = reviewDetailsUrl( + stagedReview.publicId, + installation.orgSlug, + ); + const reconciliationStartedAt = + stagedReview.finishedAt ?? stagedReview.publicationStagedAt ?? new Date(); + try { + await verifyCompletedCheckRun( + token, + payload.repoFullName, { - reviewId: stagedReview.id, - usage: reviewUsageFromEnvelope(stagedReview.envelope, { - orgId: installation.orgId, - repositoryId: repository.id, - byok: !reservation, - }), - hostedUsageReservationId: reservation?.id ?? null, - usageAccountingComplete: - stagedReview.envelope.usageAccountingComplete === true, - queueGateStateSync: false, + id: stagedReview.advisoryCheckRunId, + name: ADVISORY_CHECK_NAME, + externalId: checkRunExternalId(stagedReview.publicId, "review"), + headSha: payload.headSha, + conclusion: operationallyUnavailable ? "failure" : "success", + requireOutput: true, + detailsUrl, }, - installation.orgId, + signal, ); - if (!completion.completed) { - throw new ReviewPublicationReconciliationError( - "review publication recovery lost its terminal-state race", + if (stagedReview.status === "running") { + const completion = await finalizeStagedReviewCompletionWithGateMode( + db, + { + reviewId: stagedReview.id, + reviewClaim: input.lease, + usage: reviewUsageFromEnvelope(stagedReview.envelope, { + orgId: installation.orgId, + repositoryId: repository.id, + byok: !reservation, + }), + hostedUsageReservationId: reservation?.id ?? null, + usageAccountingComplete: + stagedReview.envelope.usageAccountingComplete === true, + queueGateStateSync: false, + }, + installation.orgId, ); + if (!completion.completed) { + throw new ReviewPublicationReconciliationError( + "review publication recovery lost its terminal-state race", + ); + } } + await reconcileReviewPublicationLifecycle({ + pool, + token, + reviewId: stagedReview.id, + reviewPublicId: stagedReview.publicId, + repositoryId: repository.id, + repoFullName: payload.repoFullName, + prNumber: payload.prNumber, + signal, + }); + void import("@/worker/runner").then(({ triggerQueueDrain }) => + triggerQueueDrain("gate-state-sync"), + ); + console.log(`review publication recovery ${stagedReview.id} completed`); + return true; + } catch (error) { + if (signal?.aborted) throw new WorkerShutdownError(); + if (error instanceof ReviewPublicationReconciliationError) throw error; + throw new ReviewPublicationReconciliationError( + redactSecrets(error), + reconciliationStartedAt, + ); } - await reconcileReviewPublicationLifecycle({ - pool, - token, - reviewId: stagedReview.id, - reviewPublicId: stagedReview.publicId, - repositoryId: repository.id, - repoFullName: payload.repoFullName, - prNumber: payload.prNumber, - signal, + } finally { + await finalizeReviewAttempt(db, { + reviewId: stagedReview.id, repoFullName: payload.repoFullName, + token: recoveryToken, installationId: payload.installationId, orgId: installation.orgId, + advisoryCheckRunId: stagedReview.advisoryCheckRunId, + gateCheckRunId: stagedReview.gateCheckRunId, + detailsUrl: reviewDetailsUrl(stagedReview.publicId, installation.orgSlug), + hostedSpend: reservation && parsed.success ? { + reservationId: reservation.id, repositoryId: repository.id, reviewId: stagedReview.id, + triggerSource: trigger.source, + usage: reviewUsageFromEnvelope(parsed.data, { orgId: installation.orgId, repositoryId: repository.id, byok: false }), + usageAccountingComplete: parsed.data.usageAccountingComplete === true, + } : undefined, }); - void import("@/worker/runner").then(({ triggerQueueDrain }) => - triggerQueueDrain("gate-state-sync"), - ); - console.log(`review publication recovery ${stagedReview.id} completed`); - return true; - } catch (error) { - if (signal?.aborted) throw new WorkerShutdownError(); - if (error instanceof ReviewPublicationReconciliationError) throw error; - throw new ReviewPublicationReconciliationError( - redactSecrets(error), - reconciliationStartedAt, - ); } } @@ -1140,6 +1176,7 @@ export async function runReviewJob( installation, repository, signal, + lease: timing.lease, }) ) { return; @@ -1197,6 +1234,7 @@ export async function runReviewJob( repoFullName: payload.repoFullName, orgSlug: installation.orgSlug, checkRunsMayExist: false, + reviewClaim: timing.lease, }, ); console.warn( @@ -1348,12 +1386,8 @@ export async function runReviewJob( queuedAt: timing.queuedAt, startedAt: timing.startedAt, }; - const inserted = ( - await db - .insert(schema.reviews) - .values({ ...reviewValues, status: "running" }) - .returning({ id: schema.reviews.id, publicId: schema.reviews.publicId }) - )[0]; + const inserted = await insertReviewForClaim(db, { ...reviewValues, status: "running" }, timing.lease); + if (!inserted && timing.lease?.receiptClaim) return; const reviewId = inserted?.id; const publicId = inserted?.publicId; if (reviewId === undefined || !publicId) @@ -1374,8 +1408,10 @@ export async function runReviewJob( let hostedUsageReservationId: string | null = null; let cliStarted = false; let completionStaged = false; - let receiptUsageForRace: ReviewCompletionInput["usage"] | undefined; - let usageAccountingCompleteForRace = false; + let publicationIncomplete = false; + let coverageCapacity: Parameters[10]; + let trustedReceiptUsage: ReviewCompletionInput["usage"] | undefined; + let trustedUsageAccountingComplete = false; let failedAttemptEnvelope: Envelope | undefined; let advisoryCheckRunMayExist = false; let gateCheckRunMayExist = false; @@ -1723,6 +1759,11 @@ export async function runReviewJob( }, maxAttempts: 5, }); + if (timing.lease?.receiptClaim) { + const client = transactionReceiptClient(tx); + const events = await prepareReviewObservation(client, reviewId, "failed", timing.lease); + await appendPreparedReceiptBatch(client, events); + } return true; }); if (settled) { @@ -1844,6 +1885,8 @@ export async function runReviewJob( repositoryId: repository.id, byok: llm.byok, }); + trustedReceiptUsage = receiptUsage; + trustedUsageAccountingComplete = ingested.usageAccountingComplete; const coverageReceipt = ingested.usageAccountingComplete && receiptUsage.length > 0 && receiptUsage.every((usage) => usage.costMicros !== null) @@ -1858,8 +1901,6 @@ export async function runReviewJob( reviewLog.line( `envelope ingested (${Buffer.byteLength(result.stdout)} bytes, ${ingested.envelope.findings.length} findings, gate ${ingested.gateFailing ? "failing" : "passing"})`, ); - receiptUsageForRace = receiptUsage; - usageAccountingCompleteForRace = ingested.usageAccountingComplete; failedAttemptEnvelope = ingested.envelope; if (isReviewCoverageCapacityFailure( ingested.envelope, @@ -1891,12 +1932,21 @@ export async function runReviewJob( { reviewId, reviewJobId: timing.lease?.id, + reviewClaim: timing.lease, envelope: ingested.envelope, configFiles, configProvenance, silent: ingested.silent, gateFailing: ingested.gateFailing, publicationReceipt, + hostedSpend: hostedUsageReservationId ? { + reservationId: hostedUsageReservationId, + repositoryId: repository.id, + reviewId, + triggerSource: reviewValues.triggerSource, + usage: receiptUsage, + usageAccountingComplete: ingested.usageAccountingComplete, + } : undefined, }, installation.orgId, ); @@ -1906,6 +1956,7 @@ export async function runReviewJob( ); } completionStaged = true; + if (staged.claimOwned === false) return; await activeLargeReviewProxy.discardCompletedRun(); reviewLog.line("review result and publication receipt staged durably"); if (snapshotChanged) { @@ -1949,6 +2000,7 @@ export async function runReviewJob( { reviewId, reviewJobId: timing.lease.id, + reviewClaim: timing.lease, repoFullName: payload.repoFullName, prNumber: payload.prNumber, headSha: payload.headSha, @@ -1994,6 +2046,7 @@ export async function runReviewJob( db, { reviewId, + reviewClaim: timing.lease, usage: receiptUsage, hostedUsageReservationId, usageAccountingComplete: ingested.usageAccountingComplete, @@ -2037,45 +2090,12 @@ export async function runReviewJob( } if (!completed) { - if (hostedUsageReservationId) { - await reconcileHostedReviewSpendFromReceipt(db, { - reservationId: hostedUsageReservationId, - repositoryId: repository.id, - reviewId, - triggerSource: reviewValues.triggerSource, - usage: receiptUsage, - usageAccountingComplete: ingested.usageAccountingComplete, - }); - } - const terminal = ( - await db - .select({ status: schema.reviews.status }) - .from(schema.reviews) - .where(eq(schema.reviews.id, reviewId)) - .limit(1) - )[0]; - if (terminal?.status === "stale") { - // A superseding webhook may have neutralized the checks while this - // CLI was still running. The CLI can PATCH them afterward, so make - // neutral the final forge state once the old process exits. - await neutralizeSupersededCheckRuns( - token, - payload.repoFullName, - advisoryCheckRunId ?? null, - gateCheckRunId ?? null, - "superseded by a newer review", - detailsUrl, - ); - reviewLog.line( - "forge check-runs restored to neutral after supersession", - ); - } console.warn( `review ${reviewId} completed after it was already superseded or failed`, ); } } catch (err) { - if (err instanceof CheckRunPublicationError && receiptUsageForRace) { + if (err instanceof CheckRunPublicationError && trustedReceiptUsage) { const terminal = ( await db .select({ status: schema.reviews.status }) @@ -2090,8 +2110,8 @@ export async function runReviewJob( repositoryId: repository.id, reviewId, triggerSource: reviewValues.triggerSource, - usage: receiptUsageForRace, - usageAccountingComplete: usageAccountingCompleteForRace, + usage: trustedReceiptUsage, + usageAccountingComplete: trustedUsageAccountingComplete, }); } await neutralizeSupersededCheckRuns( @@ -2122,14 +2142,14 @@ export async function runReviewJob( throw new ReviewPublicationReconciliationError(message); } const reconcileInterruptedSpend = async (): Promise => { - if (receiptUsageForRace) { + if (trustedReceiptUsage) { await settleFailedHostedReviewAttempt(db, { reservationId: hostedUsageReservationId, repositoryId: repository.id, reviewId, triggerSource: reviewValues.triggerSource, - usage: receiptUsageForRace, - usageAccountingComplete: usageAccountingCompleteForRace, + usage: trustedReceiptUsage, + usageAccountingComplete: trustedUsageAccountingComplete, }, largeReviewProxy); return; } @@ -2187,27 +2207,34 @@ export async function runReviewJob( reviewLog.line( "review interrupted by worker shutdown after publication began; a fresh attempt will supersede it", ); - await db - .update(schema.reviews) - .set({ - status: "stale", - errorMessage: "interrupted by an instance replacement; requeued", - finishedAt: new Date(), - }) - .where( - and( - eq(schema.reviews.id, reviewId), - eq(schema.reviews.status, "running"), - ), - ); + await db.transaction(async (tx) => { + await tx + .update(schema.reviews) + .set({ + status: "stale", + errorMessage: "interrupted by an instance replacement; requeued", + finishedAt: new Date(), + }) + .where( + and( + eq(schema.reviews.id, reviewId), + eq(schema.reviews.status, "running"), + ), + ); + if (timing.lease?.receiptClaim) { + const client = transactionReceiptClient(tx); + const events = await prepareReviewObservation(client, reviewId, "interrupted", timing.lease); + await appendPreparedReceiptBatch(client, events); + } + }); throw err; } reviewLog.line( "interrupted review spend could not be settled; failing closed", ); } - const publicationIncomplete = err instanceof CheckRunPublicationError; - const coverageCapacity = err instanceof ReviewCoverageCapacityError + publicationIncomplete = err instanceof CheckRunPublicationError; + coverageCapacity = err instanceof ReviewCoverageCapacityError ? { unreviewedHunks: err.unreviewedHunks } : undefined; const message = redactSecrets(err, sensitiveValues); @@ -2219,6 +2246,8 @@ export async function runReviewJob( envelope: failedAttemptEnvelope, }); if (rows.length === 0) return rows; + const receiptEvents = timing.lease?.receiptClaim + ? await prepareReviewObservation(transactionReceiptClient(tx), reviewId, "failed", timing.lease) : []; await tx.insert(schema.jobs).values({ kind: "check-run-cleanup", payload: { @@ -2239,6 +2268,7 @@ export async function runReviewJob( }, maxAttempts: 5, }); + await appendPreparedReceiptBatch(transactionReceiptClient(tx), receiptEvents); return rows; }); await reconcileInterruptedSpend(); @@ -2289,14 +2319,78 @@ export async function runReviewJob( throw err; } finally { if (leasePoll) clearInterval(leasePoll); - largeReviewProxy?.close(); - if (baselinePath) - await rm(baselinePath, { force: true }).catch(() => undefined); - if (workDir) - await rm(workDir, { recursive: true, force: true }).catch( - () => undefined, - ); - await reviewLog.close(); + try { + await finalizeReviewAttempt(db, { + reviewId, repoFullName: payload.repoFullName, token, gateEnabled, + advisoryCheckRunId, gateCheckRunId, detailsUrl, + expectedChecks: expectedFailureCheckRuns(publicationIncomplete), + coverageCapacity, + completedProxy: largeReviewProxy, + hostedSpend: trustedReceiptUsage ? { + reservationId: hostedUsageReservationId, + repositoryId: repository.id, + reviewId, + triggerSource: reviewValues.triggerSource, + usage: trustedReceiptUsage, + usageAccountingComplete: trustedUsageAccountingComplete, + } : undefined, + }); + } finally { + largeReviewProxy?.close(); + if (baselinePath) + await rm(baselinePath, { force: true }).catch(() => undefined); + if (workDir) + await rm(workDir, { recursive: true, force: true }).catch( + () => undefined, + ); + await reviewLog.close(); + } + } +} + +/** Settle trusted own-review spend and repair terminal checks on every attempt exit. */ +async function finalizeReviewAttempt(db: Database, input: { + reviewId: number; + repoFullName: string; + advisoryCheckRunId?: number | null; + gateCheckRunId?: number | null; + detailsUrl?: string; + token?: string; + installationId?: number; + orgId?: number | null; + gateEnabled?: boolean; + expectedChecks?: Parameters[8]; + coverageCapacity?: Parameters[10]; + completedProxy?: Pick; + hostedSpend?: Parameters[1]; +}): Promise { + try { + if (input.hostedSpend) await settleFailedHostedReviewAttempt(db, input.hostedSpend, input.completedProxy); + } finally { + if (input.advisoryCheckRunId != null || input.gateCheckRunId != null) { + const terminal = (await db + .select({ status: schema.reviews.status, errorMessage: schema.reviews.errorMessage }) + .from(schema.reviews).where(eq(schema.reviews.id, input.reviewId)).limit(1))[0]; + // A late CLI write can follow supersession or watchdog cleanup. Running + // and completed reviews retain their current publication authority. + if (terminal?.status === "stale" || terminal?.status === "failed") { + const token = input.token ?? (input.installationId === undefined ? undefined + : await getInstallationToken(input.installationId)); + if (token) { + if (terminal.status === "stale") { + await neutralizeSupersededCheckRuns(token, input.repoFullName, + input.advisoryCheckRunId ?? null, input.gateCheckRunId ?? null, + "superseded by a newer review", input.detailsUrl); + } else { + await failCheckRuns(token, input.repoFullName, input.advisoryCheckRunId, + input.gateCheckRunId, terminal.errorMessage ?? "review did not complete", + undefined, false, input.detailsUrl, input.expectedChecks, + input.gateEnabled ?? await getOrganizationGateEnabled(db, input.orgId ?? null), + input.coverageCapacity); + } + } + } + } } } @@ -2321,6 +2415,7 @@ export async function supersedeActiveReviews( const active = await db .select({ id: schema.reviews.id, + originClaimReceiptId: schema.reviews.originClaimReceiptId, publicId: schema.reviews.publicId, advisoryCheckRunId: schema.reviews.advisoryCheckRunId, gateCheckRunId: schema.reviews.gateCheckRunId, @@ -2360,16 +2455,25 @@ export async function supersedeActiveReviews( const message = input.message ?? `superseded by a newer review of ${input.newHeadSha}`; let superseded = 0; for (const review of active) { - const changed = await db - .update(schema.reviews) - .set({ status: "stale", finishedAt: new Date() }) - .where( - and( - eq(schema.reviews.id, review.id), - inArray(schema.reviews.status, ["queued", "running"]), - ), - ) - .returning({ id: schema.reviews.id }); + const update = async (tx: Database) => { + const rows = await tx + .update(schema.reviews) + .set({ status: "stale", finishedAt: new Date() }) + .where( + and( + eq(schema.reviews.id, review.id), + inArray(schema.reviews.status, ["queued", "running"]), + ), + ) + .returning({ id: schema.reviews.id }); + if (rows.length && review.originClaimReceiptId) { + const client = transactionReceiptClient(tx); + const events = await prepareReviewObservation(client, review.id, "cancelled"); + await appendPreparedReceiptBatch(client, events); + } + return rows; + }; + const changed = review.originClaimReceiptId ? await db.transaction(update) : await update(db); if (changed.length === 0) continue; superseded += 1; if (token) { diff --git a/src/worker/runner.ts b/src/worker/runner.ts index 0bc72e10..b9e4d798 100644 --- a/src/worker/runner.ts +++ b/src/worker/runner.ts @@ -317,6 +317,7 @@ export async function runClaimedJob( "worker shutdown interrupted the claim", ["review", FEEDBACK_REVIEW_JOB_KIND], [job.id], + [job], ); console.warn( `[${label}] job ${job.id} requeued after worker shutdown (${requeued})`, diff --git a/src/worker/watchdog.ts b/src/worker/watchdog.ts index 0d70b9bd..47ec3dc8 100644 --- a/src/worker/watchdog.ts +++ b/src/worker/watchdog.ts @@ -1,5 +1,8 @@ import { and, eq, lt, sql } from "drizzle-orm"; +import { + appendPreparedReceiptBatch, prepareClosedJobReceipts, prepareReviewObservation, transactionReceiptClient, +} from "@/lib/job-attempt-receipts"; import { getDb, getPool, schema, type Database } from "@/lib/db"; import { scheduleCustomerNotificationEmailJobs } from "@/lib/customer-notification-email"; import { pruneExpiredCustomerNotifications } from "@/lib/customer-notifications"; @@ -116,6 +119,7 @@ export async function failStuckReview( ) .returning({ id: schema.reviews.id }); if (rows.length === 0) return false; + const receiptEvents = await prepareReviewObservation(transactionReceiptClient(tx), review.id, "failed"); await tx.insert(schema.jobs).values({ kind: "check-run-cleanup", payload: { @@ -135,6 +139,7 @@ export async function failStuckReview( }, maxAttempts: 5, }); + await appendPreparedReceiptBatch(transactionReceiptClient(tx), receiptEvents); return true; }); } @@ -249,7 +254,12 @@ export async function watchdogPass( // Full feedback reviews carry publication recovery and coalesced snapshots. // Feedback polling jobs use the generic attempts budget below. const pool = getPool(); - await pool.query( + const client = await pool.connect(); + try { + await client.query("BEGIN"); + const locked = await client.query(`SELECT id::text FROM jobs + WHERE status = 'running' AND locked_at < $1 ORDER BY id FOR UPDATE`, [cutoff]); + await client.query( `WITH updated AS ( UPDATE jobs SET status = CASE @@ -307,7 +317,14 @@ export async function watchdogPass( FROM updated WHERE kind = 'respond' AND status = 'failed'`, [cutoff, COALESCED_REVIEW_PAYLOAD_KEY, FEEDBACK_REVIEW_JOB_KIND], - ); + ); + const events = await prepareClosedJobReceipts(client, locked.rows.map((row) => row.id)); + await appendPreparedReceiptBatch(client, events); + await client.query("COMMIT"); + } catch (error) { + await client.query("ROLLBACK").catch(() => undefined); + throw error; + } finally { client.release(); } const scheduledFeedbackReconciliations = await scheduleFindingFeedbackReconciliationJobs( pool, diff --git a/tests/job-attempt-receipts.test.ts b/tests/job-attempt-receipts.test.ts new file mode 100644 index 00000000..721855e8 --- /dev/null +++ b/tests/job-attempt-receipts.test.ts @@ -0,0 +1,1228 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { SQL } from "bun"; +import { randomBytes, randomUUID } from "node:crypto"; +import { + appendPreparedReceiptBatch, readAttemptReceiptPageSql, receiptAppendLockSql, + receiptHeadSql, stageOwnedReviewSql, type PreparedReceipt, type ReceiptClient, +} from "../src/lib/job-attempt-receipts"; + +const port = process.env.POSTIL_RECEIPT_TEST_PORT; +type Rows = Record[]; +const connections: Array<{ close(): Promise }> = []; +async function connect() { + const pool = new SQL({ hostname: "localhost", port: Number(port), username: "postgres", + database: process.env.POSTIL_RECEIPT_TEST_DATABASE ?? "postgres", + max: 1, idleTimeout: 0, connectionTimeout: 2 }); + const reserved = await pool.reserve(); + let terminated = false; + const client = { + async query(text: string, values: unknown[] = []): Promise<{ rows: Rows }> { + return { rows: Array.from(await reserved.unsafe(text, values as never[])) }; + }, + markTerminated() { terminated = true; }, + async close() { + // A backend killed by a fault test has already closed its socket. Bun's + // release of that dead reservation raises an unrelated teardown error. + if (terminated) return; + reserved.release(); await pool.close({ timeout: 0.1 }); + }, + }; + connections.push(client); + return client; +} +let db: Awaited>; +let repository: string; +const query = async (text: string, values: unknown[] = []) => (await db.query(text, values)).rows; +async function transaction(client: ReceiptClient, body: () => Promise): Promise { + await client.query("BEGIN ISOLATION LEVEL READ COMMITTED"); + try { const result = await body(); await client.query("COMMIT"); return result; } + catch (error) { await client.query("ROLLBACK"); throw error; } +} +async function job(kind = "review") { + return (await query("INSERT INTO jobs(kind,payload) VALUES($1,'{}') RETURNING id::text", [kind]))[0]!.id as string; +} +function event(jobId: string, ordinal: number, claimId: string | null, + changes: Partial = {}): PreparedReceipt { + return { jobId, ordinal, jobKind: "review", claimId, reviewId: null, + reviewPublicId: null, eventKind: "claim", outcome: "started", phase: null, + category: null, recovered: null, recovery: null, ...changes }; +} +async function claim(jobId: string, kind: "review" | "review-feedback" = "review") { + return transaction(db, async () => { + const rows = await query(`UPDATE jobs SET status='running', attempts=attempts+1, + locked_by='fixture-worker', locked_at=now() WHERE id=$1::bigint AND status='queued' + RETURNING attempts`, [jobId]); + if (!rows.length) return null; + const ordinal = rows[0]!.attempts as number; + const [id] = await appendPreparedReceiptBatch(db, [event(jobId, ordinal, null, { jobKind: kind })]); + await query("UPDATE jobs SET active_claim_receipt_id=$2::bigint WHERE id=$1::bigint", [jobId, id]); + return { id: id!, ordinal, jobId, kind }; + }); +} +type Claim = NonNullable>>; +async function closeClaim(c: Claim, outcome: PreparedReceipt["outcome"], reset = false) { + return transaction(db, async () => { + const rows = await query(`UPDATE jobs SET status=$3::job_status, active_claim_receipt_id=NULL, + locked_by=NULL, locked_at=NULL, attempts=CASE WHEN $4 THEN 0 ELSE attempts END + WHERE id=$1::bigint AND active_claim_receipt_id=$2::bigint AND status='running' + AND locked_by='fixture-worker' RETURNING id`, + [c.jobId, c.id, ["retry", "continued", "interrupted", "deferred"].includes(outcome) ? "queued" : "done", reset]); + if (!rows.length) return false; + await appendPreparedReceiptBatch(db, [event(c.jobId, c.ordinal, c.id, + { jobKind: c.kind, eventKind: "queue_closed", outcome })]); + return true; + }); +} +async function review(c: Claim) { + return transaction(db, async () => { + await query("SELECT id FROM jobs WHERE id=$1::bigint FOR UPDATE", [c.jobId]); + const [r] = await query(`INSERT INTO reviews(repository_id,pr_number,head_sha,base_sha,status,origin_claim_receipt_id) + VALUES($1::bigint,1,$2,$3,'running',$4::bigint) RETURNING id::text,public_id::text`, + [repository, randomBytes(20).toString("hex"), randomBytes(20).toString("hex"), c.id]); + await appendPreparedReceiptBatch(db, [event(c.jobId, c.ordinal, c.id, + { jobKind: c.kind, eventKind: "review_bound", outcome: "bound", reviewId: r!.id, reviewPublicId: r!.public_id })]); + return r!; + }); +} +async function observe(c: Claim, r: Record, outcome: PreparedReceipt["outcome"]) { + return transaction(db, async () => { + const rows = await query(`UPDATE reviews SET status=$2::review_status + WHERE id=$1::bigint AND status='running' RETURNING id`, [r.id, outcome === "staged" ? "running" : outcome]); + if (!rows.length) return []; + return appendPreparedReceiptBatch(db, [event(c.jobId, c.ordinal, c.id, + { jobKind: c.kind, eventKind: "review_observed", outcome, reviewId: r.id, reviewPublicId: r.public_id })]); + }); +} +async function stage(c: Claim, r: Record) { + return transaction(db, async () => { + await query("SELECT id FROM jobs WHERE id=$1::bigint FOR UPDATE", [c.jobId]); + await query("SELECT id FROM reviews WHERE id=$1::bigint FOR UPDATE", [r.id]); + return (await query(stageOwnedReviewSql, [c.jobId, c.id, "fixture-worker", r.id])).length; + }); +} + +describe.skipIf(!port)("disposable PostgreSQL receipt SQL", () => { + beforeAll(async () => { + db = await connect(); + const [marker] = await query(`SELECT shobj_description(oid,'pg_database') AS marker + FROM pg_database WHERE datname=current_database()`); + if (marker?.marker !== "disposable receipt qualification") throw new Error("dedicated fixture required"); + const [installation] = await query(`INSERT INTO installations(github_installation_id,account_login,account_type) + VALUES($1,'receipt-test','Organization') RETURNING id::text`, [Date.now()]); + const [repo] = await query(`INSERT INTO repositories(installation_id,github_repo_id,full_name) + VALUES($1::bigint,$2,'receipt-test/fixture') RETURNING id::text`, [installation!.id, Date.now()]); + repository = repo!.id; + }); + afterAll(async () => { await Promise.all(connections.map((c) => c.close())); }); + + test("allocator is CACHE 1, positive, noncycling and rejects cache drift", async () => { + const [s] = await query("SELECT seqcache::text,seqcycle,seqincrement::text FROM pg_sequence WHERE seqrelid='job_attempt_receipt_ids'::regclass"); + expect(s).toEqual({ seqcache: "1", seqcycle: false, seqincrement: "1" }); + await query("ALTER SEQUENCE job_attempt_receipt_ids CACHE 2"); + try { await expect(claim(await job())).rejects.toThrow(); } + finally { await query("ALTER SEQUENCE job_attempt_receipt_ids CACHE 1"); } + }); + + test("same worker and reused ordinal cannot close or stage a newer claim", async () => { + const j = await job(); const a = (await claim(j))!; const ra = await review(a); + expect(await stage(a, ra)).toBe(1); + expect(await closeClaim(a, "interrupted", true)).toBe(true); + const b = (await claim(j))!; const rb = await review(b); + expect(b.ordinal).toBe(a.ordinal); expect(b.id).not.toBe(a.id); + expect(await stage(b, rb)).toBe(1); + expect(await stage(a, ra)).toBe(0); + expect(await closeClaim(a, "succeeded")).toBe(false); + expect((await query("SELECT staged_review_id::text,active_claim_receipt_id::text,payload FROM jobs WHERE id=$1::bigint", [j]))[0]) + .toEqual({ staged_review_id: rb.id, active_claim_receipt_id: b.id, payload: { recoveryReviewId: Number(rb.id) } }); + // The genuine historical review can retain its own observation under A. + expect((await observe(a, ra, "staged")).length).toBe(1); + expect(await stage(a, rb)).toBe(0); + expect(await closeClaim(b, "succeeded")).toBe(true); + expect(await closeClaim(b, "succeeded")).toBe(false); + }); + + test("failure then success retains separate claims/reviews and finite incident", async () => { + const j = await job("review-feedback"); const a = (await claim(j, "review-feedback"))!; + const ra = await review(a); + await transaction(db, async () => { + await query("UPDATE reviews SET status='failed' WHERE id=$1::bigint", [ra.id]); + await appendPreparedReceiptBatch(db, [ + event(j, a.ordinal, a.id, { jobKind: a.kind, eventKind: "incident", outcome: "observed", + reviewId: ra.id, reviewPublicId: ra.public_id, phase: "review", category: "invalidOutput", recovered: false }), + event(j, a.ordinal, a.id, { jobKind: a.kind, eventKind: "review_observed", outcome: "failed", + reviewId: ra.id, reviewPublicId: ra.public_id }), + ]); + }); + expect(await closeClaim(a, "retry")).toBe(true); + const b = (await claim(j, "review-feedback"))!; const rb = await review(b); + await observe(b, rb, "completed"); expect(await closeClaim(b, "succeeded")).toBe(true); + expect((await query("SELECT event_kind,outcome FROM job_attempt_receipts WHERE job_id=$1::bigint ORDER BY receipt_id", [j])) + .map((r) => `${r.event_kind}:${r.outcome}`)).toEqual([ + "claim:started", "review_bound:bound", "incident:observed", "review_observed:failed", "queue_closed:retry", + "claim:started", "review_bound:bound", "review_observed:completed", "queue_closed:succeeded", + ]); + expect((await observe(b, rb, "completed")).length).toBe(0); + }); + + test("append failure rolls business state back; aborted sequence gaps are safe", async () => { + const j = await job(); const c = (await claim(j))!; const before = (await query(receiptHeadSql))[0]!.receipt_id; + await expect(transaction(db, async () => { + await query("UPDATE jobs SET status='done',active_claim_receipt_id=NULL WHERE id=$1::bigint", [j]); + await appendPreparedReceiptBatch(db, [event(j, c.ordinal + 1, c.id, { eventKind: "queue_closed", outcome: "succeeded" })]); + })).rejects.toThrow(); + expect((await query("SELECT status,active_claim_receipt_id::text FROM jobs WHERE id=$1::bigint", [j]))[0]) + .toEqual({ status: "running", active_claim_receipt_id: c.id }); + expect(await closeClaim(c, "succeeded")).toBe(true); + const [last] = await query("SELECT r.receipt_id::text,r.predecessor_id::text FROM job_attempt_receipts r ORDER BY r.receipt_id DESC LIMIT 1"); + expect(last!.predecessor_id).toBe(before); + expect(BigInt(last!.receipt_id) - BigInt(before)).toBeGreaterThan(1n); + }); + + test("publication recovery binds a new claim to the genuine staged review", async () => { + const j = await job(); const a = (await claim(j))!; const r = await review(a); + expect(await stage(a, r)).toBe(1); await closeClaim(a, "interrupted", true); + const b = (await claim(j))!; + await transaction(db, async () => { + await query("SELECT id FROM jobs WHERE id=$1::bigint FOR UPDATE", [j]); + await query("SELECT id FROM reviews WHERE id=$1::bigint FOR UPDATE", [r.id]); + await appendPreparedReceiptBatch(db, [event(j, b.ordinal, b.id, { eventKind: "review_bound", outcome: "bound", + reviewId: r.id, reviewPublicId: r.public_id })]); + }); + expect(await stage(b, r)).toBe(1); + expect((await query("SELECT origin_claim_receipt_id::text FROM reviews WHERE id=$1::bigint", [r.id]))[0]!.origin_claim_receipt_id).toBe(a.id); + await observe(b, r, "completed"); await closeClaim(b, "succeeded"); + }); + + test("constraints reject wrong job, wrong claim kind, missing binding and mutable origin", async () => { + const a = (await claim(await job()))!; const b = (await claim(await job()))!; const r = await review(a); + const invalid = [ + event(b.jobId, a.ordinal, a.id, { eventKind: "queue_closed", outcome: "succeeded" }), + event(a.jobId, a.ordinal, a.id, { eventKind: "review_observed", outcome: "failed", reviewId: r.id, reviewPublicId: randomUUID() }), + event(b.jobId, b.ordinal, b.id, { eventKind: "review_bound", outcome: "bound", reviewId: r.id, reviewPublicId: r.public_id }), + ]; + for (const e of invalid) await expect(transaction(db, () => appendPreparedReceiptBatch(db, [e]))).rejects.toThrow(); + await expect(query("UPDATE reviews SET origin_claim_receipt_id=$2::bigint WHERE id=$1::bigint", [r.id, b.id])).rejects.toThrow(); + await expect(query("UPDATE job_attempt_receipts SET outcome=outcome WHERE receipt_id=$1::bigint", [a.id])).rejects.toThrow(); + await expect(query("DELETE FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [a.id])).rejects.toThrow(); + await expect(query("TRUNCATE job_attempt_receipts CASCADE")).rejects.toThrow(); + const [binding] = await query("SELECT receipt_id::text FROM job_attempt_receipts WHERE claim_receipt_id=$1::bigint AND event_kind='review_bound'", [a.id]); + await expect(transaction(db, () => appendPreparedReceiptBatch(db, [event(a.jobId, a.ordinal, binding!.receipt_id, + { eventKind: "queue_closed", outcome: "succeeded" })]))).rejects.toThrow(); + expect(await closeClaim(a, "succeeded")).toBe(true); + await expect(transaction(db, () => appendPreparedReceiptBatch(db, [event(a.jobId, a.ordinal, a.id, + { eventKind: "queue_closed", outcome: "succeeded" })]))).rejects.toThrow(); + }); + + test("claim history does not cascade or restrict synthetic business row deletion", async () => { + const c = (await claim(await job()))!; const r = await review(c); + await closeClaim(c, "succeeded"); + await query("DELETE FROM reviews WHERE id=$1::bigint", [r.id]); + await query("DELETE FROM jobs WHERE id=$1::bigint", [c.jobId]); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE job_id=$1::bigint", [c.jobId])).length).toBe(3); + }); + + test("delayed concurrent append uses a fresh predecessor after waiting", async () => { + const a = await connect(); const b = await connect(); + const ja = await job(); const jb = await job(); + for (const [client, id] of [[a, ja], [b, jb]] as const) { + await client.query("BEGIN"); + await client.query("UPDATE jobs SET status='running',attempts=1 WHERE id=$1::bigint", [id]); + } + const oldHead = (await b.query(receiptHeadSql)).rows[0]!.receipt_id; + const [ia] = await appendPreparedReceiptBatch(a, [event(ja, 1, null)]); + const pending = appendPreparedReceiptBatch(b, [event(jb, 1, null)]); + await query("SELECT pg_sleep(0.05)"); + expect((await query(receiptHeadSql))[0]!.receipt_id).toBe(oldHead); + expect((await query("SELECT pid FROM pg_locks WHERE locktype='advisory' AND classid=17491 AND NOT granted")).length).toBe(1); + await a.query("COMMIT"); + const [ib] = await pending; await b.query("COMMIT"); + expect(BigInt(ib!)).toBeGreaterThan(BigInt(ia!)); + expect((await query("SELECT predecessor_id::text FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [ib]))[0]!.predecessor_id).toBe(ia); + }); + + test("prepared multi-row batch is invisible until one commit", async () => { + const writer = await connect(); const jobs = [await job(), await job()]; + await writer.query("BEGIN"); + for (const id of jobs) await writer.query("UPDATE jobs SET status='running',attempts=1 WHERE id=$1::bigint", [id]); + const ids = await appendPreparedReceiptBatch(writer, jobs.map((id) => event(id, 1, null))); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE job_id=ANY($1::bigint[])", ["{" + jobs.join(",") + "}"])).length).toBe(0); + await writer.query("COMMIT"); + expect((await query("SELECT r.receipt_id::text FROM job_attempt_receipts r WHERE job_id=ANY($1::bigint[]) ORDER BY r.receipt_id", ["{" + jobs.join(",") + "}"])).map((r) => r.receipt_id)).toEqual(ids); + }); + + test("stalled writer has bounded wait; independent business work proceeds", async () => { + const holder = await connect(); const waiter = await connect(); + await holder.query("BEGIN"); await holder.query(receiptAppendLockSql); + await waiter.query("BEGIN"); await waiter.query("SET LOCAL lock_timeout='100ms'"); + const started = performance.now(); + await expect(waiter.query(receiptAppendLockSql)).rejects.toThrow(); + expect(performance.now() - started).toBeLessThan(1500); + await waiter.query("ROLLBACK"); + const j = await job(); + expect((await query("UPDATE jobs SET payload=payload || '{\"fixture\":true}'::jsonb WHERE id=$1::bigint RETURNING id", [j])).length).toBe(1); + // A finite reader does not wait for the writer's advisory lock. + expect((await query(readAttemptReceiptPageSql, ["0"])).length).toBeLessThanOrEqual(64); + await holder.query("ROLLBACK"); + expect((await claim(j))!.id).toBeString(); + }); + + test("backend death before commit rolls back state and releases append lock", async () => { + const writer = await connect(); const j = await job(); + const pid = (await writer.query("SELECT pg_backend_pid() AS pid")).rows[0]!.pid; + await writer.query("BEGIN"); + await writer.query("UPDATE jobs SET status='running',attempts=1 WHERE id=$1::bigint", [j]); + const [id] = await appendPreparedReceiptBatch(writer, [event(j, 1, null)]); + writer.markTerminated(); await query("SELECT pg_terminate_backend($1::integer, 1000)", [pid]); + expect((await query("SELECT pid FROM pg_stat_activity WHERE pid=$1::integer", [pid])).length).toBe(0); + expect((await query("SELECT status,attempts FROM jobs WHERE id=$1::bigint", [j]))[0]).toEqual({ status: "queued", attempts: 0 }); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [id])).length).toBe(0); + expect((await claim(j))!.id).toBeString(); + }); + + test("idle transaction expiry releases a stalled append tail", async () => { + const writer = await connect(); + const pid = (await writer.query("SELECT pg_backend_pid() AS pid")).rows[0]!.pid; + await writer.query("BEGIN"); await writer.query("SET LOCAL idle_in_transaction_session_timeout='100ms'"); + await writer.query(receiptAppendLockSql); + writer.markTerminated(); + await query("SELECT pg_sleep(0.2)"); + expect((await query("SELECT pid FROM pg_stat_activity WHERE pid=$1::integer", [pid])).length).toBe(0); + expect((await claim(await job()))!.id).toBeString(); + }); + + test("backend death during COMMIT rolls back the deferred transaction", async () => { + const c = (await claim(await job()))!; const writer = await connect(); + const pid = (await writer.query("SELECT pg_backend_pid() AS pid")).rows[0]!.pid; + await query(`CREATE FUNCTION receipt_fixture_commit_pause() RETURNS trigger LANGUAGE plpgsql AS + $$ BEGIN PERFORM pg_sleep(1); RETURN NEW; END $$`); + await query(`CREATE CONSTRAINT TRIGGER receipt_fixture_commit_pause AFTER INSERT ON job_attempt_receipts + DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN (NEW.event_kind='queue_closed') + EXECUTE FUNCTION receipt_fixture_commit_pause()`); + try { + await writer.query("BEGIN"); + await writer.query("UPDATE jobs SET status='done',active_claim_receipt_id=NULL WHERE id=$1::bigint", [c.jobId]); + await appendPreparedReceiptBatch(writer, [event(c.jobId, c.ordinal, c.id, { eventKind: "queue_closed", outcome: "succeeded" })]); + const committing = writer.query("COMMIT").then(() => true, () => false); + await query("SELECT pg_sleep(0.05)"); + expect((await query("SELECT wait_event FROM pg_stat_activity WHERE pid=$1::integer", [pid]))[0]!.wait_event).toBe("PgSleep"); + writer.markTerminated(); await query("SELECT pg_terminate_backend($1::integer)", [pid]); + expect(await committing).toBe(false); + expect((await query("SELECT status,active_claim_receipt_id::text FROM jobs WHERE id=$1::bigint", [c.jobId]))[0]) + .toEqual({ status: "running", active_claim_receipt_id: c.id }); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE claim_receipt_id=$1::bigint AND event_kind='queue_closed'", [c.id])).length).toBe(0); + } finally { + await query("DROP TRIGGER receipt_fixture_commit_pause ON job_attempt_receipts"); + await query("DROP FUNCTION receipt_fixture_commit_pause()"); + } + expect(await closeClaim(c, "succeeded")).toBe(true); + // Reconciliation after a committed transition returns no second occurrence. + expect(await closeClaim(c, "succeeded")).toBe(false); + }); + + test("repeatable-read and autocommit cannot append authoritative receipts", async () => { + const writer = await connect(); const j = await job(); + await writer.query("BEGIN ISOLATION LEVEL REPEATABLE READ"); + await expect(appendPreparedReceiptBatch(writer, [event(j, 1, null)])).rejects.toThrow(); + await writer.query("ROLLBACK"); + await expect(appendPreparedReceiptBatch(writer, [event(j, 1, null)])).rejects.toThrow(); + }); + + test("reader lock timeout rolls back and releases its transaction", async () => { + const holder = await connect(); const reader = await connect(); + const pid = (await reader.query("SELECT pg_backend_pid() AS pid")).rows[0]!.pid; + await holder.query("BEGIN"); await holder.query("LOCK job_attempt_receipts IN ACCESS EXCLUSIVE MODE"); + await reader.query("BEGIN READ ONLY"); await reader.query("SET LOCAL lock_timeout='100ms'"); + const start = performance.now(); + await expect(reader.query(readAttemptReceiptPageSql, ["0"])).rejects.toThrow(); + expect(performance.now() - start).toBeLessThan(1500); + await reader.query("ROLLBACK"); await holder.query("ROLLBACK"); + const [state] = await query("SELECT state,xact_start FROM pg_stat_activity WHERE pid=$1::integer", [pid]); + expect(state).toEqual({ state: "idle", xact_start: null }); + expect((await claim(await job()))!.id).toBeString(); + }); + + test("legacy writers remain available and do not manufacture receipts", async () => { + const j = await job(); + await query("UPDATE jobs SET status='running',attempts=attempts+1,locked_by='fixture-legacy' WHERE id=$1::bigint", [j]); + await query("UPDATE jobs SET status='done',locked_by=NULL,last_error=NULL WHERE id=$1::bigint", [j]); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE job_id=$1::bigint", [j])).length).toBe(0); + const c = (await claim(await job()))!; + // An old administrative writer can still transition a new claim; that + // transition has no receipt and fleet completeness must remain unknown. + await query("UPDATE jobs SET status='queued',locked_by=NULL WHERE id=$1::bigint", [c.jobId]); + expect(await closeClaim(c, "succeeded")).toBe(false); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE job_id=$1::bigint", [c.jobId])).length).toBe(1); + }); + + test("a crash between review and queue commits leaves a partial history", async () => { + const c = (await claim(await job()))!; const r = await review(c); + await observe(c, r, "failed"); + expect((await query("SELECT outcome FROM job_attempt_receipts WHERE job_id=$1::bigint AND event_kind='queue_closed'", [c.jobId])).length).toBe(0); + expect((await query("SELECT status FROM jobs WHERE id=$1::bigint", [c.jobId]))[0]!.status).toBe("running"); + await closeClaim(c, "interrupted"); + expect((await query("SELECT outcome FROM job_attempt_receipts WHERE job_id=$1::bigint ORDER BY receipt_id", [c.jobId])).map((r) => r.outcome)) + .toEqual(["started", "bound", "failed", "interrupted"]); + }); + + test("append-only guard prevents suffix loss; a privileged loss is visible only when acknowledged", async () => { + const c = (await claim(await job()))!; await closeClaim(c, "succeeded"); + const suffix = (await query(receiptHeadSql))[0]!.receipt_id; + await expect(query("DELETE FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [suffix])).rejects.toThrow(); + // Fault injection is restricted to the dedicated disposable database. + await query("BEGIN"); + try { + await query("ALTER TABLE job_attempt_receipts DISABLE TRIGGER job_attempt_receipts_immutable"); + await query("DELETE FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [suffix]); + const acknowledged = (await query(readAttemptReceiptPageSql, [suffix]))[0]!; + expect(acknowledged.anchor_id).toBeNull(); + expect(BigInt(acknowledged.head_id)).toBeLessThan(BigInt(suffix)); + const unseen = (await query(readAttemptReceiptPageSql, [c.id]))[0]!; + expect(unseen.anchor_id).toBe(c.id); + expect(unseen.receipt_id).toBeNull(); + // An unseen suffix loss supplies no detectable discontinuity by itself. + } finally { await query("ROLLBACK"); } + expect((await query(receiptHeadSql))[0]!.receipt_id).toBe(suffix); + }); + + test("a simultaneous staging/reclaim race preserves the new target", async () => { + const j = await job(); const a = (await claim(j))!; const ra = await review(a); + await closeClaim(a, "interrupted", true); + const b = (await claim(j))!; const rb = await review(b); + const late = await connect(); const owner = await connect(); + await owner.query("BEGIN"); await owner.query("SELECT id FROM jobs WHERE id=$1::bigint FOR UPDATE", [j]); + await owner.query(stageOwnedReviewSql, [j, b.id, "fixture-worker", rb.id]); + await late.query("BEGIN"); + const pending = late.query(stageOwnedReviewSql, [j, a.id, "fixture-worker", ra.id]); + await query("SELECT pg_sleep(0.05)"); await owner.query("COMMIT"); + expect((await pending).rows.length).toBe(0); await late.query("COMMIT"); + expect((await query("SELECT staged_review_id::text FROM jobs WHERE id=$1::bigint", [j]))[0]!.staged_review_id).toBe(rb.id); + }); + + test("seeded stateful histories preserve one closure and immutable claim fencing", async () => { + let seed = 173; + const random = () => (seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0); + const outcomes = ["retry", "continued", "interrupted", "deferred", "cancelled", "coalesced", "succeeded"] as const; + for (let history = 0; history < 20; history++) { + const j = await job(); const claims: Claim[] = []; + for (let step = 0; step < 8; step++) { + const c = (await claim(j))!; claims.push(c); + for (const stale of claims.slice(0, -1)) expect(await closeClaim(stale, "succeeded")).toBe(false); + const outcome = step === 7 ? "succeeded" : outcomes[random() % 4]!; + expect(await closeClaim(c, outcome, Boolean(random() % 2))).toBe(true); + expect(await closeClaim(c, outcome)).toBe(false); + } + expect(new Set(claims.map((c) => c.id)).size).toBe(8); + expect((await query("SELECT receipt_id FROM job_attempt_receipts WHERE job_id=$1::bigint AND event_kind='queue_closed'", [j])).length).toBe(8); + } + }, 20000); + + test("privacy projection rejects getters and ignores generated hostile content", async () => { + const j = await job(); const c = (await claim(j))!; const r = await review(c); + let getterCalls = 0; + const hostile = randomUUID() + '\n{"trusted":true,"jobId":"' + randomBytes(16).toString("hex") + '"}\u001b[2J'; + await query("UPDATE jobs SET payload=jsonb_build_object('message',$2::text),last_error=$2 WHERE id=$1::bigint", [j, hostile]); + await query("UPDATE reviews SET error_message=$2,envelope=jsonb_build_object('modelOutput',$2::text) WHERE id=$1::bigint", [r.id, hostile]); + const forged = event(j, 1, c.id); + Object.defineProperty(forged, "outcome", { enumerable: true, get() { getterCalls++; return "started"; } }); + await expect(transaction(db, () => appendPreparedReceiptBatch(db, [forged]))).rejects.toThrow(); + expect(getterCalls).toBe(0); + const anchor = (await query("SELECT predecessor_id::text FROM job_attempt_receipts WHERE receipt_id=$1::bigint", [c.id]))[0]!.predecessor_id; + const page = await query(readAttemptReceiptPageSql, [anchor]); + expect(page.some((row) => row.claim_receipt_id === c.id)).toBe(true); + expect(page.length).toBeLessThanOrEqual(64); + expect(Buffer.byteLength(JSON.stringify(page))).toBeLessThan(65536); + expect(JSON.stringify(page)).not.toContain(hostile); + expect(Object.keys(page[0]!)).not.toContain("payload"); + expect(Object.keys(page[0]!)).not.toContain("error_message"); + }); + + test("recovery semantics and repeated incidents are enforced in PostgreSQL", async () => { + const c = (await claim(await job()))!; const r = await review(c); + const incident = event(c.jobId, c.ordinal, c.id, { eventKind: "incident", outcome: "observed", + reviewId: r.id, reviewPublicId: r.public_id, phase: "review", category: "invalidOutput", recovered: false }); + await expect(transaction(db, () => appendPreparedReceiptBatch(db, [{ ...incident, recovery: "repair" }]))).rejects.toThrow(); + await expect(transaction(db, () => appendPreparedReceiptBatch(db, [{ ...incident, recovered: true }]))).rejects.toThrow(); + const ids = await transaction(db, () => appendPreparedReceiptBatch(db, [incident, incident, { ...incident, recovered: true, recovery: "repair" }])); + expect(new Set(ids).size).toBe(3); + }); + + test("large decimal identities retain numeric order and timestamps do not advance a cursor", async () => { + const [floor] = await query(`SELECT setval('job_attempt_receipt_ids', + GREATEST((SELECT receipt_id+1 FROM job_attempt_receipts ORDER BY receipt_id DESC LIMIT 1),9007199254740993),false)::text AS id`); + await query("ALTER TABLE job_attempt_receipts ALTER COLUMN occurred_at SET DEFAULT '2030-01-01T00:00:00Z'"); + const a = (await claim(await job()))!; const b = (await claim(await job()))!; + await query("ALTER TABLE job_attempt_receipts ALTER COLUMN occurred_at SET DEFAULT '2000-01-01T00:00:00Z'"); + let c: Claim; + try { c = (await claim(await job()))!; } + finally { await query("ALTER TABLE job_attempt_receipts ALTER COLUMN occurred_at SET DEFAULT clock_timestamp()"); } + expect(a.id).toBe(floor!.id); expect(BigInt(b.id)).toBe(BigInt(a.id) + 1n); + const rows = await query(readAttemptReceiptPageSql, [a.id]); + expect(rows.map((r) => r.receipt_id)).toEqual([b.id, c!.id]); + expect(rows[0]!.occurred_at.startsWith("2030-")).toBe(true); + expect(rows[1]!.occurred_at.startsWith("2000-")).toBe(true); + expect(rows[0]!.predecessor_id).toBe(a.id); + // Reading and accepting a prefix are separate operations in this harness. + const accepted = b.id; + expect((await query(readAttemptReceiptPageSql, [accepted]))[0]!.receipt_id).toBe(c!.id); + expect((await query(readAttemptReceiptPageSql, [a.id]))[0]!.receipt_id).toBe(b.id); + }); + + test("finite page has indexed range/genesis/anchor/backwards-head access", async () => { + await query("ANALYZE job_attempt_receipts"); + const explain = await query("EXPLAIN (ANALYZE, FORMAT JSON) " + readAttemptReceiptPageSql, ["9007199254740993"]); + const plan = JSON.stringify(explain); + expect(plan).toContain("job_attempt_receipts_pkey"); + expect(plan).toContain("Index Cond"); + expect(plan).toContain("Backward"); + expect(plan).not.toContain("Seq Scan"); + expect((await query(readAttemptReceiptPageSql, ["0"])).length).toBe(64); + }); +}); + +// Production driver and API checks share only the explicitly marked fixture. +import { Pool } from "pg"; +import { createServer, connect as connectSocket } from "node:net"; +import { drizzle } from "drizzle-orm/node-postgres"; +import * as schema from "../src/lib/db/schema"; +import { + claimJob, completeJob, continueClaimedJob, failJob, requeueJobsOwnedBy, + retryJobIndefinitely, externalSideEffectLeaseActive, cancelPullRequestPublication, + type ClaimedJob, +} from "../src/lib/queue"; +import { + insertReviewForClaim, bindStagedReviewClaim, stageReviewCompletionCandidate, + finalizeStagedReviewCompletionWithGateMode, +} from "../src/lib/review-completion"; +import { claimPausedHostedReview } from "../src/lib/hosted-review-pause"; +import { deferHostedReviewForRelease } from "../src/lib/release-job-rollout"; +import { consumePrivateWorkerRehearsalAfterStaging } from "../src/lib/private-worker-rehearsal"; +import { + readAdmittedReceiptPage, closeReceiptAdmission, admitReceiptSource, acknowledgeReceiptPage, + recordReceiptPageReported, receiptPageAdmitted, type ReceiptAdmissionState, +} from "../src/lib/job-attempt-receipts"; +import type { Envelope } from "../src/lib/envelope"; + +describe.skipIf(!port)("production receipt ownership APIs on PostgreSQL", () => { + let pool: Pool; + let database: ReturnType>; + const rows = async (text: string, values: unknown[] = []) => (await pool.query(text, values)).rows; + beforeAll(async () => { + pool = new Pool({ host: "127.0.0.1", port: Number(port), user: "postgres", + database: process.env.POSTIL_RECEIPT_TEST_DATABASE, max: 8, + connectionTimeoutMillis: 2000, statement_timeout: 6000, application_name: "receipt-api-fixture" }); + database = drizzle(pool, { schema }); + const [marker] = await rows(`SELECT shobj_description(oid,'pg_database') AS marker + FROM pg_database WHERE datname=current_database()`); + if (marker?.marker !== "disposable receipt qualification") throw new Error("dedicated fixture required"); + if (!repository) { + const [installation] = await rows(`INSERT INTO installations(github_installation_id,account_login,account_type) + VALUES($1,'receipt-test','Organization') RETURNING id`, [Date.now()]); + const [repo] = await rows(`INSERT INTO repositories(installation_id,github_repo_id,full_name) + VALUES($1,$2,'receipt-test/api') RETURNING id::text`, [installation.id, Date.now()]); + repository = repo.id; + } + }); + afterAll(async () => { await pool?.end(); }); + async function start(payload: Record = {}, kind = "review"): Promise { + await rows("UPDATE jobs SET run_after='infinity' WHERE status='queued'"); + const [j] = await rows("INSERT INTO jobs(kind,payload,max_attempts) VALUES($2,$1,5) RETURNING id::text", [payload, kind]); + const c = await claimJob(pool, "reused-worker", [kind]); + expect(c?.receiptClaim?.jobId).toBe(j.id); + expect(Object.isFrozen(c!.receiptClaim)).toBe(true); + return c!; + } + const values = () => ({ repositoryId: Number(repository), prNumber: 1, + headSha: randomBytes(20).toString("hex"), baseSha: randomBytes(20).toString("hex"), status: "running" as const }); + function envelope(): Envelope { + return { version: 1, summary: randomUUID(), silent: true, findings: [], resolved: [], + counts: { info: 0, warn: 0, error: 0, suppressed: 0, ungrounded: 0 }, + confidenceBuckets: [0, 0, 0, 0, 0], gate: { failOn: "error", failing: false }, + modelUsed: "fixture/model", usage: { promptTokens: 3, completionTokens: 2 }, durationMs: 1, + baseSha: randomBytes(20).toString("hex"), headSha: randomBytes(20).toString("hex"), sinceSha: null }; + } + function stageInput(c: ClaimedJob, reviewId: number, e = envelope()) { + return { reviewId, reviewJobId: c.id, reviewClaim: c, envelope: e, + configFiles: [], silent: true, gateFailing: false, + publicationReceipt: { version: 1 as const, receiptId: randomUUID(), findings: [] } }; + } + async function replace(c: ClaimedJob) { + expect(await requeueJobsOwnedBy(pool, c.lockedBy, "fixture interruption", [c.kind], [c.id], [c])).toBe(1); + const next = await claimJob(pool, c.lockedBy, [c.kind]); + expect(next!.attempts).toBe(c.attempts); + expect(next!.receiptClaim!.receiptId).not.toBe(c.receiptClaim!.receiptId); + return next!; + } + const pointers = (c: ClaimedJob) => rows(`SELECT active_claim_receipt_id::text, staged_review_id::text, + payload, status, attempts, locked_by, locked_at FROM jobs WHERE id=$1::bigint`, [c.receiptClaim!.jobId]); + + async function losingSpend(stageFirst = false, kind = "review") { + const a = await start({}, kind); const review = (await insertReviewForClaim(database, values(), a))!; + const [org] = await rows("INSERT INTO organizations(name,slug) VALUES($1,$2) RETURNING id", ["Receipt fixture", randomUUID()]); + const [reservation] = await rows(`INSERT INTO hosted_usage_reservations(org_id,review_id,reserved_micros,expires_at) + VALUES($1,$2,1000,now()+interval '1 hour') RETURNING id`, [org.id, review.id]); + const spend = { reservationId: reservation.id as string, repositoryId: Number(repository), reviewId: review.id, + triggerSource: "unknown" as const, usageAccountingComplete: true, + usage: [{ orgId: Number(org.id), repositoryId: Number(repository), promptTokens: 3, completionTokens: 2, + modelUsed: "fixture/model", costMicros: 7, billingScope: "private_hosted" as const }] }; + const input = { ...stageInput(a, review.id), hostedSpend: spend }; + if (stageFirst) await stageReviewCompletionCandidate(database, input, null); + const b = await replace(a); + if (stageFirst) expect(await bindStagedReviewClaim(database, review.id, b)).toBe(true); + else { + const other = (await insertReviewForClaim(database, values(), b))!; + await stageReviewCompletionCandidate(database, stageInput(b, other.id), null); + } + return { a, b, review, input, spend }; + } + const spendState = async (reviewId: number) => (await rows(`SELECT r.status,r.actual_micros::text, + (SELECT count(*)::int FROM usage_events u WHERE u.review_id=r.review_id) AS usage_count, + (SELECT COALESCE(sum(cost_micros),0)::text FROM usage_events u WHERE u.review_id=r.review_id) AS cost + FROM hosted_usage_reservations r WHERE r.review_id=$1`, [reviewId]))[0]; + + test("losing stage settles its own receipt once and append failure rolls back the settlement", async () => { + const { b, review, input } = await losingSpend(); const before = await pointers(b); + await expect(stageReviewCompletionCandidate(database, { ...input, + hostedSpend: { ...input.hostedSpend, repositoryId: Number(repository) + 1 } }, null)).rejects.toThrow("identity mismatch"); + await rows(`CREATE FUNCTION receipt_fixture_spend_reject() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN RAISE EXCEPTION 'fixture append rejection'; END $$`); + await rows(`CREATE TRIGGER receipt_fixture_spend_reject BEFORE INSERT ON job_attempt_receipts + FOR EACH ROW EXECUTE FUNCTION receipt_fixture_spend_reject()`); + try { + await expect(stageReviewCompletionCandidate(database, input, null)).rejects.toThrow(); + expect(await spendState(review.id)).toEqual({ status: "active", actual_micros: null, usage_count: 0, cost: "0" }); + expect((await rows("SELECT envelope FROM reviews WHERE id=$1", [review.id]))[0].envelope).toBeNull(); + expect((await rows("SELECT count(*)::int AS n FROM review_publication_receipts WHERE review_id=$1", [review.id]))[0].n).toBe(0); + expect(await pointers(b)).toEqual(before); + } finally { + await rows("DROP TRIGGER receipt_fixture_spend_reject ON job_attempt_receipts"); + await rows("DROP FUNCTION receipt_fixture_spend_reject()"); + } + const results = await Promise.all([0, 1].map(() => stageReviewCompletionCandidate(database, input, null))); + expect(results.every((r) => r.staged && r.claimOwned === false)).toBe(true); + expect(await spendState(review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await pointers(b)).toEqual(before); + expect((await rows("SELECT count(*)::int AS n FROM job_attempt_receipts WHERE review_id=$1 AND outcome='staged'", [review.id]))[0].n).toBe(1); + expect(await completeJob(pool, b)).toBe("done"); + }); + + test("genuine recovery finalizes after the late producer already settles the same receipt", async () => { + const { b, review, input, spend } = await losingSpend(true); + expect((await stageReviewCompletionCandidate(database, input, null)).claimOwned).toBe(false); + const accounting = { reviewId: review.id, reviewClaim: b, hostedUsageReservationId: spend.reservationId, + usage: spend.usage, usageAccountingComplete: true }; + expect((await finalizeStagedReviewCompletionWithGateMode(database, accounting, null)).completed).toBe(true); + expect((await finalizeStagedReviewCompletionWithGateMode(database, accounting, null)).completed).toBe(false); + expect(await spendState(review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await completeJob(pool, b)).toBe("done"); + }); + + test("terminal staging settles once across current and lost claims without accepting stale evidence", async () => { + for (const status of ["stale", "failed", "completed"]) { + for (const recoveryOwns of [false, true]) { + const x = await losingSpend(recoveryOwns); + const input = recoveryOwns ? { ...x.input, reviewClaim: x.b } : x.input; + await rows("UPDATE reviews SET status=$2,finished_at=now() WHERE id=$1", [x.review.id, status]); + const before = await pointers(x.b); + const evidence = await rows("SELECT envelope,gate_failing FROM reviews WHERE id=$1", [x.review.id]); + const receipts = await rows("SELECT receipt_id::text FROM job_attempt_receipts WHERE review_id=$1 ORDER BY receipt_id", [x.review.id]); + await expect(stageReviewCompletionCandidate(database, { ...input, + hostedSpend: { ...x.spend, reviewId: x.review.id + 1 } }, null)).rejects.toThrow("identity mismatch"); + const results = await Promise.all([0, 1].map(() => stageReviewCompletionCandidate(database, input, null))); + expect(results.every((r) => r.staged === false && r.completed === false)).toBe(true); + expect(await spendState(x.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await pointers(x.b)).toEqual(before); + expect((await rows("SELECT status FROM reviews WHERE id=$1", [x.review.id]))[0].status).toBe(status); + expect(await rows("SELECT envelope,gate_failing FROM reviews WHERE id=$1", [x.review.id])).toEqual(evidence); + expect(await rows("SELECT receipt_id::text FROM job_attempt_receipts WHERE review_id=$1 ORDER BY receipt_id", [x.review.id])).toEqual(receipts); + expect(await completeJob(pool, x.b)).toBe("done"); + } + } + }); + + test("terminal settlement rolls back on usage failure and backend death, then retries once", async () => { + const x = await losingSpend(); + await rows("UPDATE reviews SET status='stale',finished_at=now() WHERE id=$1", [x.review.id]); + const before = await pointers(x.b); + await rows(`CREATE FUNCTION receipt_terminal_usage_reject() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN RAISE EXCEPTION 'fixture usage rejection'; END $$`); + await rows(`CREATE TRIGGER receipt_terminal_usage_reject BEFORE INSERT ON usage_events + FOR EACH ROW EXECUTE FUNCTION receipt_terminal_usage_reject()`); + try { + await expect(stageReviewCompletionCandidate(database, x.input, null)).rejects.toThrow(); + expect(await spendState(x.review.id)).toEqual({ status: "active", actual_micros: null, usage_count: 0, cost: "0" }); + expect(await pointers(x.b)).toEqual(before); + } finally { + await rows("DROP TRIGGER receipt_terminal_usage_reject ON usage_events"); + await rows("DROP FUNCTION receipt_terminal_usage_reject()"); + } + const blocker = await pool.connect(); + const producer = new Pool({ host: "127.0.0.1", port: Number(port), user: "postgres", + database: process.env.POSTIL_RECEIPT_TEST_DATABASE, max: 1, statement_timeout: 4000 }); + let inserting!: () => void; + const reached = new Promise((resolve) => { inserting = resolve; }); + producer.on("connect", (client) => { + client.on("error", () => undefined); + const original = client.query.bind(client); + client.query = ((...args: any[]) => { + if ((typeof args[0] === "string" ? args[0] : args[0]?.text)?.startsWith('insert into "usage_events"')) inserting(); + return (original as Function)(...args); + }) as typeof client.query; + }); + try { + const pid = (await producer.query("SELECT pg_backend_pid() AS pid")).rows[0].pid; + await blocker.query("BEGIN"); await blocker.query("LOCK TABLE usage_events IN ACCESS EXCLUSIVE MODE"); + const pending = stageReviewCompletionCandidate(drizzle(producer, { schema }), x.input, null).then(() => false, () => true); + await reached; + expect((await rows("SELECT status,actual_micros FROM hosted_usage_reservations WHERE review_id=$1", [x.review.id]))[0]) + .toEqual({ status: "active", actual_micros: null }); + await rows("SELECT pg_terminate_backend($1,1000)", [pid]); + expect(await pending).toBe(true); + await blocker.query("COMMIT"); + expect(await spendState(x.review.id)).toEqual({ status: "active", actual_micros: null, usage_count: 0, cost: "0" }); + expect(await pointers(x.b)).toEqual(before); + expect((await stageReviewCompletionCandidate(database, x.input, null)).staged).toBe(false); + expect((await stageReviewCompletionCandidate(database, x.input, null)).staged).toBe(false); + expect(await spendState(x.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + } finally { await blocker.query("ROLLBACK"); blocker.release(); await producer.end(); } + }); + + test("B wins staging: concurrent stale A retains only A evidence and cannot mutate B", async () => { + const a = await start(); const ra = (await insertReviewForClaim(database, values(), a))!; + const b = await replace(a); const rb = (await insertReviewForClaim(database, values(), b))!; + expect((await stageReviewCompletionCandidate(database, stageInput(b, rb.id), null)).claimOwned).toBe(true); + const before = await pointers(b); + const results = await Promise.all([ + stageReviewCompletionCandidate(database, stageInput(a, ra.id), null), + completeJob(pool, a), failJob(pool, a, "stale", { permanent: true }), + retryJobIndefinitely(pool, a, "stale"), + continueClaimedJob(pool, a, {}).then(() => false, () => true), + requeueJobsOwnedBy(pool, a.lockedBy, "stale", ["review"], [a.id], [a]), + ]); + expect(results[0]).toMatchObject({ staged: true, claimOwned: false }); + expect(results.slice(1)).toEqual(["lost", "lost", "lost", true, 0]); + expect(await pointers(b)).toEqual(before); + expect(await consumePrivateWorkerRehearsalAfterStaging(pool, { + reviewId: ra.id, reviewJobId: a.id, reviewClaim: a, + repoFullName: "fixture/rehearsal", prNumber: 1, headSha: randomBytes(20).toString("hex"), + workerInstanceId: "reused-worker", sandbox: { orgSlug: "fixture", repoFullName: "fixture/rehearsal" }, + })).toBeNull(); + expect(await pointers(b)).toEqual(before); + expect((await rows(`SELECT claim_receipt_id::text FROM job_attempt_receipts + WHERE review_id=$1 AND event_kind='review_observed'`, [ra.id]))).toEqual([{ claim_receipt_id: a.receiptClaim!.receiptId }]); + expect(await externalSideEffectLeaseActive(pool, a)).toBe(false); + expect(await externalSideEffectLeaseActive(pool, b)).toBe(true); + expect(await completeJob(pool, b)).toBe("done"); + expect(await completeJob(pool, b)).toBe("lost"); + }); + + test("A stages first: B binds the genuine target and accounts once with real reservation effects", async () => { + const a = await start(); const r = (await insertReviewForClaim(database, values(), a))!; + const e = envelope(); + e.modelIncidents = [0, 1].map(() => ({ phase: "review", category: "timeout", recovered: true, recovery: "repair" })); + const input = stageInput(a, r.id, e); + expect((await stageReviewCompletionCandidate(database, input, null)).claimOwned).toBe(true); + expect((await stageReviewCompletionCandidate(database, input, null)).staged).toBe(true); + expect((await rows("SELECT count(*)::int AS n FROM job_attempt_receipts WHERE review_id=$1 AND event_kind='incident'", [r.id]))[0].n).toBe(2); + const b = await replace(a); + expect(await bindStagedReviewClaim(database, r.id, b)).toBe(true); + expect(await bindStagedReviewClaim(database, r.id, b)).toBe(true); + const [org] = await rows("INSERT INTO organizations(name,slug) VALUES($1,$2) RETURNING id", ["Receipt fixture", randomUUID()]); + const [reservation] = await rows(`INSERT INTO hosted_usage_reservations(org_id,review_id,reserved_micros,expires_at) + VALUES($1,$2,1000,now()+interval '1 hour') RETURNING id`, [org.id, r.id]); + const completion = { reviewId: r.id, reviewClaim: b, hostedUsageReservationId: reservation.id, + usageAccountingComplete: true, usage: [{ orgId: Number(org.id), repositoryId: Number(repository), + promptTokens: 3, completionTokens: 2, modelUsed: "fixture/model", costMicros: 7, billingScope: "private_hosted" as const }] }; + expect((await finalizeStagedReviewCompletionWithGateMode(database, completion, null)).completed).toBe(true); + expect((await finalizeStagedReviewCompletionWithGateMode(database, completion, null)).completed).toBe(false); + expect((await rows("SELECT status,actual_micros::text FROM hosted_usage_reservations WHERE id=$1", [reservation.id]))[0]) + .toEqual({ status: "reconciled", actual_micros: "7" }); + expect((await rows("SELECT count(*)::int AS n FROM usage_events WHERE review_id=$1", [r.id]))[0].n).toBe(1); + expect((await rows("SELECT origin_claim_receipt_id::text FROM reviews WHERE id=$1", [r.id]))[0].origin_claim_receipt_id).toBe(a.receiptClaim!.receiptId); + expect(await completeJob(pool, b)).toBe("done"); + }); + + test("raw decimal job identity survives numeric compatibility and origin cannot be reassigned", async () => { + await rows("UPDATE jobs SET run_after='infinity' WHERE status='queued'"); + const raw = (9007199254740993n + BigInt(randomBytes(4).readUInt32BE()) * 2n).toString(); + await rows("INSERT INTO jobs(id,kind,payload) OVERRIDING SYSTEM VALUE VALUES($1,'review','{}')", [raw]); + const a = (await claimJob(pool, "raw-fixture", ["review"]))!; + expect(a.receiptClaim!.jobId).toBe(raw); + const review = (await insertReviewForClaim(database, values(), a))!; + await expect(rows("UPDATE reviews SET origin_claim_receipt_id=NULL WHERE id=$1", [review.id])).rejects.toThrow("immutable review origin"); + expect((await stageReviewCompletionCandidate(database, stageInput(a, review.id), null)).claimOwned).toBe(true); + expect(await completeJob(pool, a)).toBe("done"); + expect((await rows("SELECT job_id::text FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [a.receiptClaim!.receiptId]))[0].job_id).toBe(raw); + }); + + test("same-job history and a forged payload cannot authorize a new binding", async () => { + const a = await start(); const r = (await insertReviewForClaim(database, values(), a))!; + const b = await replace(a); + await rows("UPDATE jobs SET payload=jsonb_build_object('recoveryReviewId',$2::bigint) WHERE id=$1", [b.id, r.id]); + expect(await bindStagedReviewClaim(database, r.id, b)).toBe(false); + await expect(stageReviewCompletionCandidate(database, stageInput(b, r.id), null)).rejects.toThrow("binding is missing"); + const altered = { ...b, lockedAt: new Date(b.lockedAt.getTime() + 1) }; + expect(await completeJob(pool, altered)).toBe("lost"); + expect(await externalSideEffectLeaseActive(pool, altered)).toBe(false); + expect(await completeJob(pool, b)).toBe("done"); + }); + + test("legacy-origin recovery remains available but still honors the new owner's lease", async () => { + const c = await start(); + const r = (await insertReviewForClaim(database, values()))!; + const input = stageInput(c, r.id); + await stageReviewCompletionCandidate(database, { ...input, reviewClaim: undefined }, null); + const accounting = { reviewId: r.id, reviewClaim: c, usageAccountingComplete: true, + usage: [{ orgId: null, repositoryId: Number(repository), promptTokens: 0, completionTokens: 0, + modelUsed: "fixture/model", costMicros: 0, billingScope: "analytics" as const }] }; + expect((await finalizeStagedReviewCompletionWithGateMode(database, { ...accounting, + reviewClaim: { ...c, lockedAt: new Date(c.lockedAt.getTime() + 1) } }, null)).completed).toBe(false); + expect((await finalizeStagedReviewCompletionWithGateMode(database, accounting, null)).completed).toBe(true); + expect((await rows("SELECT origin_claim_receipt_id FROM reviews WHERE id=$1", [r.id]))[0].origin_claim_receipt_id).toBeNull(); + expect((await rows("SELECT count(*)::int AS n FROM job_attempt_receipts WHERE review_id=$1", [r.id]))[0].n).toBe(0); + expect(await completeJob(pool, c)).toBe("done"); + }); + + test("append rejection rolls back claim, staging, publication and queue effects", async () => { + const a = await start(); const r = (await insertReviewForClaim(database, values(), a))!; + const before = await pointers(a); + await rows(`CREATE FUNCTION receipt_fixture_reject() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN RAISE EXCEPTION 'fixture append rejection'; END $$`); + await rows(`CREATE TRIGGER receipt_fixture_reject BEFORE INSERT ON job_attempt_receipts + FOR EACH ROW EXECUTE FUNCTION receipt_fixture_reject()`); + try { + await expect(stageReviewCompletionCandidate(database, stageInput(a, r.id), null)).rejects.toThrow(); + expect(await pointers(a)).toEqual(before); + expect((await rows("SELECT envelope FROM reviews WHERE id=$1", [r.id]))[0].envelope).toBeNull(); + await expect(completeJob(pool, a)).rejects.toThrow(); + expect(await pointers(a)).toEqual(before); + await rows("INSERT INTO jobs(kind,payload) VALUES('review','{}')"); + await expect(claimJob(pool, "failure", ["review"])).rejects.toThrow(); + expect((await rows("SELECT count(*)::int AS n FROM jobs WHERE locked_by='failure'"))[0].n).toBe(0); + } finally { + await rows("DROP TRIGGER receipt_fixture_reject ON job_attempt_receipts"); + await rows("DROP FUNCTION receipt_fixture_reject()"); + } + expect(await completeJob(pool, a)).toBe("done"); + }); + + test("staging holds the job fence while blocked at append; backend death rolls back a claim", async () => { + const a = await start(); const r = (await insertReviewForClaim(database, values(), a))!; + const blocker = await pool.connect(); + await blocker.query("BEGIN"); await blocker.query(receiptAppendLockSql); + let reached!: () => void; + const waiting = new Promise((resolve) => { reached = resolve; }); + const producer = new Pool({ host: "127.0.0.1", port: Number(port), user: "postgres", + database: process.env.POSTIL_RECEIPT_TEST_DATABASE, max: 1, statement_timeout: 4000 }); + producer.on("connect", (client) => { + client.on("error", () => undefined); + const original = client.query.bind(client); + client.query = ((...args: any[]) => { + if ((typeof args[0] === "string" ? args[0] : args[0]?.text)?.includes("pg_advisory_xact_lock(17491")) reached(); + return (original as Function)(...args); + }) as typeof client.query; + }); + try { + const staged = stageReviewCompletionCandidate(drizzle(producer, { schema }), stageInput(a, r.id), null); + await waiting; + const contender = await pool.connect(); + await contender.query("BEGIN"); await contender.query("SET LOCAL lock_timeout='100ms'"); + await expect(contender.query("SELECT id FROM jobs WHERE id=$1 FOR UPDATE", [a.id])).rejects.toMatchObject({ code: "55P03" }); + await contender.query("ROLLBACK"); contender.release(); + await blocker.query("COMMIT"); + expect((await staged).claimOwned).toBe(true); + const b = await replace(a); + expect(await bindStagedReviewClaim(database, r.id, b)).toBe(true); + expect(await completeJob(pool, b)).toBe("done"); + await rows("UPDATE jobs SET run_after='infinity' WHERE status='queued'"); + const [j] = await rows("INSERT INTO jobs(kind,payload) VALUES('review','{}') RETURNING id::text"); + await blocker.query("BEGIN"); await blocker.query(receiptAppendLockSql); + const dying = await producer.connect(); + const pid = (await dying.query("SELECT pg_backend_pid() AS pid")).rows[0].pid; + dying.release(); + const reachedAgain = new Promise((resolve) => { reached = resolve; }); + const pending = claimJob(producer, "crash-fixture", ["review"]); + const failure = pending.then(() => false, () => true); + await reachedAgain; + await rows("SELECT pg_terminate_backend($1)", [pid]); + expect(await failure).toBe(true); + await blocker.query("COMMIT"); + expect((await rows("SELECT status,attempts,active_claim_receipt_id FROM jobs WHERE id=$1", [j.id]))[0]) + .toEqual({ status: "queued", attempts: 0, active_claim_receipt_id: null }); + await rows("UPDATE jobs SET run_after=now() WHERE id=$1", [j.id]); + const retry = (await claimJob(pool, "crash-fixture", ["review"]))!; + expect(retry.receiptClaim!.jobId).toBe(j.id); + expect(await completeJob(pool, retry)).toBe("done"); + const lost = await losingSpend(); const before = await pointers(lost.b); + const stageBackend = (await producer.query("SELECT pg_backend_pid() AS pid")).rows[0].pid; + await blocker.query("BEGIN"); await blocker.query(receiptAppendLockSql); + const settlementReached = new Promise((resolve) => { reached = resolve; }); + const pendingStage = stageReviewCompletionCandidate(drizzle(producer, { schema }), lost.input, null); + const failedStage = pendingStage.then(() => false, () => true); + await settlementReached; + expect(await spendState(lost.review.id)).toEqual({ status: "active", actual_micros: null, usage_count: 0, cost: "0" }); + await rows("SELECT pg_terminate_backend($1,1000)", [stageBackend]); + expect(await failedStage).toBe(true); await blocker.query("COMMIT"); + expect(await spendState(lost.review.id)).toEqual({ status: "active", actual_micros: null, usage_count: 0, cost: "0" }); + expect(await pointers(lost.b)).toEqual(before); + expect((await stageReviewCompletionCandidate(database, lost.input, null)).claimOwned).toBe(false); + expect(await spendState(lost.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + } finally { await blocker.query("ROLLBACK"); blocker.release(); await producer.end(); } + }); + + test("wire-lost COMMIT is reconciled from one durable claim rather than replayed", async () => { + await rows("UPDATE jobs SET run_after='infinity' WHERE status='queued'"); + const [j] = await rows("INSERT INTO jobs(kind,payload) VALUES('review','{}') RETURNING id::text"); + let dropped = 0; + const sockets = new Set>(); + const proxy = createServer((front) => { + const back = connectSocket({ host: "127.0.0.1", port: Number(port) }); + sockets.add(front); sockets.add(back); + front.on("data", (bytes) => back.write(bytes)); + front.on("error", () => back.destroy()); back.on("error", () => front.destroy()); + front.on("close", () => { sockets.delete(front); back.destroy(); }); + back.on("close", () => { sockets.delete(back); front.destroy(); }); + let buffered = Buffer.alloc(0); + back.on("data", (bytes) => { + buffered = Buffer.concat([buffered, Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes)]); + while (buffered.length >= 5) { + const size = buffered.readUInt32BE(1) + 1; + if (buffered.length < size) break; + const frame = buffered.subarray(0, size); buffered = buffered.subarray(size); + if (frame[0] === 67 && frame.subarray(5).toString() === "COMMIT\0") { + dropped++; front.destroy(); back.destroy(); return; + } + front.write(frame); + } + }); + }); + await new Promise((resolve) => proxy.listen(0, "127.0.0.1", resolve)); + const address = proxy.address(); + if (!address || typeof address === "string") throw new Error("fixture proxy unavailable"); + const wire = new Pool({ host: "127.0.0.1", port: address.port, user: "postgres", + database: process.env.POSTIL_RECEIPT_TEST_DATABASE, connectionTimeoutMillis: 2000 }); + wire.on("connect", (client) => client.on("error", () => undefined)); + try { + await expect(claimJob(wire, "wire-fixture", ["review"])).rejects.toThrow(); + expect(dropped).toBe(1); + const [persisted] = await rows("SELECT status,attempts,active_claim_receipt_id::text FROM jobs WHERE id=$1", [j.id]); + expect(persisted.status).toBe("running"); expect(persisted.attempts).toBe(1); + expect((await rows("SELECT count(*)::int AS n FROM job_attempt_receipts WHERE job_id=$1 AND event_kind='claim'", [j.id]))[0].n).toBe(1); + expect(persisted.active_claim_receipt_id).not.toBeNull(); + // No retry is performed after the unknown wire result. + const lost = await losingSpend(); const before = await pointers(lost.b); + await expect(stageReviewCompletionCandidate(drizzle(wire, { schema }), lost.input, null)).rejects.toThrow(); + expect(dropped).toBe(2); + expect(await spendState(lost.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await pointers(lost.b)).toEqual(before); + expect((await stageReviewCompletionCandidate(database, lost.input, null)).claimOwned).toBe(false); + expect(await spendState(lost.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + const terminal = await losingSpend(); const terminalBefore = await pointers(terminal.b); + await rows("UPDATE reviews SET status='stale',finished_at=now() WHERE id=$1", [terminal.review.id]); + await expect(stageReviewCompletionCandidate(drizzle(wire, { schema }), terminal.input, null)).rejects.toThrow(); + expect(dropped).toBe(3); + expect(await spendState(terminal.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect((await stageReviewCompletionCandidate(database, terminal.input, null)).staged).toBe(false); + expect(await spendState(terminal.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await pointers(terminal.b)).toEqual(terminalBefore); + } finally { + await wire.end(); for (const socket of sockets) socket.destroy(); + await new Promise((resolve) => proxy.close(() => resolve())); + } + }); + + test("legacy callers stay available; typed cancellation and deferral close the actual claim", async () => { + const a = await start({ installationId: 101, sourceInstallationId: 102, sourceOrgId: 103, + githubRepoId: 104, repoFullName: "fixture/repository", prNumber: 1 }); + expect(await cancelPullRequestPublication(pool, { installationId: 101, sourceInstallationId: 102, + sourceOrgId: 103, githubRepoId: 104, repoFullName: "fixture/repository", prNumber: 1 })).toBe(1); + expect(await completeJob(pool, a)).toBe("lost"); + expect((await rows("SELECT outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [a.receiptClaim!.receiptId]))[0].outcome).toBe("cancelled"); + const b = await start(); + await deferHostedReviewForRelease(pool, b, randomBytes(20).toString("hex")); + expect((await rows("SELECT outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [b.receiptClaim!.receiptId]))[0].outcome).toBe("deferred"); + const c = await start(); + const legacy = { id: c.id, lockedBy: c.lockedBy }; + expect(await completeJob(pool, legacy)).toBe("done"); + // The old writer remains callable, and does not manufacture a qualified closure. + expect((await rows("SELECT count(*)::int AS n FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [c.receiptClaim!.receiptId]))[0].n).toBe(0); + }); + + test("generated actual queue transitions retain one closure per claim across ordinal reuse", async () => { + for (let seed = 1; seed <= 12; seed++) { + let current = await start(); + for (let step = 0; step < 4; step++) { + const operation = (seed * 17 + step * 13) % 4; + const id = current.receiptClaim!.receiptId; + let expected: string; + if (operation === 0) { await continueClaimedJob(pool, current, { fixture: randomUUID() }); expected = "continued"; } + else if (operation === 1) { expect(await retryJobIndefinitely(pool, current, randomBytes(12).toString("base64"))).toBe("retried"); expected = "retry"; } + else if (operation === 2) { expect(await failJob(pool, current, "fixture retry")).toBe("retried"); expected = "retry"; } + else { expect(await requeueJobsOwnedBy(pool, current.lockedBy, "fixture interruption", ["review"], [current.id], [current])).toBe(1); expected = "interrupted"; } + expect(await completeJob(pool, current)).toBe("lost"); + expect(await rows("SELECT outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [id])).toEqual([{ outcome: expected }]); + await rows("UPDATE jobs SET run_after=now() WHERE id=$1::bigint", [current.receiptClaim!.jobId]); + const next = (await claimJob(pool, current.lockedBy, ["review"]))!; + expect(next.receiptClaim!.receiptId).not.toBe(id); + current = next; + } + expect(await completeJob(pool, current)).toBe("done"); + } + }); + + test("staging and finalization take the decision scope before their claim row", async () => { + for (const operation of ["stage", "finalize"] as const) { + const c = await start(); + const review = (await insertReviewForClaim(database, values(), c))!; + const input = stageInput(c, review.id); + if (operation === "finalize") await stageReviewCompletionCandidate(database, input, null); + const control = await pool.connect(); + let pending: Promise | undefined; + try { + await control.query("BEGIN"); + const pid = (await control.query("SELECT pg_backend_pid() AS pid")).rows[0].pid; + const [repo] = await rows("SELECT github_repo_id::text FROM repositories WHERE id=$1", [repository]); + await control.query("SELECT pg_advisory_xact_lock(hashtextextended($1,0))", [`postil:review-pr:${repo.github_repo_id}\u001f1`]); + pending = operation === "stage" + ? stageReviewCompletionCandidate(database, input, null) + : finalizeStagedReviewCompletionWithGateMode(database, { + reviewId: review.id, reviewClaim: c, usage: [], usageAccountingComplete: true, + }, null); + let blocked = false; + for (let tries = 0; tries < 100 && !blocked; tries++) { + blocked = (await rows(`SELECT EXISTS (SELECT 1 FROM pg_stat_activity + WHERE $1::int=ANY(pg_blocking_pids(pid))) AS blocked`, [pid]))[0].blocked; + if (!blocked) await Bun.sleep(10); + } + expect(blocked).toBe(true); + expect((await control.query("SELECT id FROM jobs WHERE id=$1 FOR UPDATE NOWAIT", [c.id])).rows).toHaveLength(1); + } finally { + await control.query("ROLLBACK"); + control.release(); + await pending; + } + expect(await completeJob(pool, c)).toBe("done"); + } + }); + + test("completion waits for feedback control before locking its claim and closes once", async () => { + const c = await start(); + const control = await pool.connect(); + let completion: Promise | undefined; + try { + await control.query("BEGIN"); + const pid = (await control.query("SELECT pg_backend_pid() AS pid")).rows[0].pid; + await control.query("SELECT id FROM review_feedback_control WHERE id=1 FOR UPDATE"); + completion = completeJob(pool, c); + let blocked = false; + for (let tries = 0; tries < 100 && !blocked; tries++) { + blocked = (await rows(`SELECT EXISTS (SELECT 1 FROM pg_stat_activity + WHERE $1::int=ANY(pg_blocking_pids(pid))) AS blocked`, [pid]))[0].blocked; + if (!blocked) await Bun.sleep(10); + } + expect(blocked).toBe(true); + expect((await control.query("SELECT id FROM jobs WHERE id=$1 FOR UPDATE NOWAIT", [c.id])).rows).toHaveLength(1); + await control.query("COMMIT"); + expect((await Promise.all([completion, completeJob(pool, c)])).sort()).toEqual(["done", "lost"]); + expect(await rows("SELECT outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [c.receiptClaim!.receiptId])) + .toEqual([{ outcome: "succeeded" }]); + } finally { + await control.query("ROLLBACK"); + control.release(); + await completion?.catch(() => undefined); + } + }); + + test("receipt rejection rolls back coalesced completion and retries without duplicate work", async () => { + const pending = { repoFullName: `fixture/${randomUUID()}`, githubRepoId: Date.now(), prNumber: 1, + headSha: randomBytes(20).toString("hex"), baseSha: randomBytes(20).toString("hex") }; + const c = await start({ _postilCoalescedReviewPayload: pending }); + await rows(`CREATE FUNCTION fixture_reject_completion() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN IF NEW.event_kind='queue_closed' THEN RAISE EXCEPTION 'fixture closure rejection'; END IF; + RETURN NEW; END $$`); + await rows("CREATE TRIGGER fixture_reject_completion BEFORE INSERT ON job_attempt_receipts FOR EACH ROW EXECUTE FUNCTION fixture_reject_completion()"); + try { + let failure: unknown; + try { await completeJob(pool, c); } catch (error) { failure = error; } + expect(String(failure)).toContain("fixture closure rejection"); + expect(await rows("SELECT status,active_claim_receipt_id::text FROM jobs WHERE id=$1", [c.id])) + .toEqual([{ status: "running", active_claim_receipt_id: c.receiptClaim!.receiptId }]); + expect(await rows("SELECT id FROM jobs WHERE payload=$1::jsonb", [JSON.stringify(pending)])).toHaveLength(0); + } finally { + await rows("DROP TRIGGER fixture_reject_completion ON job_attempt_receipts"); + await rows("DROP FUNCTION fixture_reject_completion()"); + } + expect(await completeJob(pool, c)).toBe("coalesced"); + expect(await completeJob(pool, c)).toBe("lost"); + expect(await rows("SELECT id FROM jobs WHERE payload=$1::jsonb", [JSON.stringify(pending)])).toHaveLength(1); + expect(await rows("SELECT outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [c.receiptClaim!.receiptId])) + .toEqual([{ outcome: "coalesced" }]); + }); + + test("both review kinds fence generated same-label reclaims and preserve receipt kinds", async () => { + for (const kind of ["review", "review-feedback"]) { + for (let seed = 0; seed < 6; seed++) { + let a = await start({}, kind); + for (let step = 0; step < 4; step++) { + const operation = (seed * 7 + step * 3) % 4; + if (operation === 0) await continueClaimedJob(pool, a, { fixture: randomUUID() }); + else if (operation === 1) await retryJobIndefinitely(pool, a, randomUUID()); + else if (operation === 2) await failJob(pool, a, randomUUID()); + else await requeueJobsOwnedBy(pool, a.lockedBy, randomUUID(), [kind], [a.id], [a]); + await rows("UPDATE jobs SET run_after=now() WHERE id=$1", [a.id]); + const b = (await claimJob(pool, a.lockedBy, [kind]))!; + expect(b.receiptClaim!.receiptId).not.toBe(a.receiptClaim!.receiptId); + expect(b.lockedBy).toBe(a.lockedBy); + const before = await pointers(b); + expect(await completeJob(pool, a)).toBe("lost"); + expect(await pointers(b)).toEqual(before); + expect(await rows(`SELECT job_kind FROM job_attempt_receipts WHERE claim_receipt_id=$1 + AND event_kind='queue_closed'`, [a.receiptClaim!.receiptId])).toEqual([{ job_kind: kind }]); + a = b; + } + expect(await completeJob(pool, a)).toBe("done"); + expect(await rows("SELECT DISTINCT job_kind FROM job_attempt_receipts WHERE job_id=$1", [a.id])) + .toEqual([{ job_kind: kind }]); + } + } + }); + + test("both review kinds preserve losing-stage rollback, own usage once and genuine recovery", async () => { + for (const kind of ["review", "review-feedback"]) { + for (const first of [false, true]) { + const x = await losingSpend(first, kind); const before = await pointers(x.b); + if (!first) x.input.envelope.modelIncidents = [{ phase: "review", category: "providerError", recovered: true, recovery: "fallback" }]; + await rows(`CREATE FUNCTION fixture_reject_kind_stage() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN IF NEW.event_kind='review_observed' THEN RAISE EXCEPTION 'fixture kind stage rejection'; END IF; + RETURN NEW; END $$`); + await rows("CREATE TRIGGER fixture_reject_kind_stage BEFORE INSERT ON job_attempt_receipts FOR EACH ROW EXECUTE FUNCTION fixture_reject_kind_stage()"); + try { + if (!first) { + let rejection: unknown; + try { await stageReviewCompletionCandidate(database, x.input, null); } catch (error) { rejection = error; } + expect(String((rejection as { cause?: Error })?.cause)).toContain("fixture kind stage rejection"); + expect((await spendState(x.review.id)).usage_count).toBe(0); + expect((await rows("SELECT envelope FROM reviews WHERE id=$1", [x.review.id]))[0].envelope).toBeNull(); + expect(await pointers(x.b)).toEqual(before); + } + } finally { + await rows("DROP TRIGGER fixture_reject_kind_stage ON job_attempt_receipts"); + await rows("DROP FUNCTION fixture_reject_kind_stage()"); + } + const results = await Promise.all([0, 1].map(() => stageReviewCompletionCandidate(database, x.input, null))); + expect(results.every(r => r.staged && r.claimOwned === false)).toBe(true); + expect(await completeJob(pool, x.a)).toBe("lost"); + expect(await pointers(x.b)).toEqual(before); + if (first) { + expect((await finalizeStagedReviewCompletionWithGateMode(database, { reviewId: x.review.id, + reviewClaim: x.b, usage: x.spend.usage, hostedUsageReservationId: x.spend.reservationId, + usageAccountingComplete: true }, null)).completed).toBe(true); + } + expect(await spendState(x.review.id)).toEqual({ status: "reconciled", actual_micros: "7", usage_count: 1, cost: "7" }); + expect(await rows("SELECT DISTINCT job_kind FROM job_attempt_receipts WHERE job_id=$1", [x.b.id])) + .toEqual([{ job_kind: kind }]); + expect(await completeJob(pool, x.b)).toBe("done"); + } + const c = await start({}, kind); + await deferHostedReviewForRelease(pool, c, randomBytes(20).toString("hex")); + expect(await rows("SELECT job_kind,outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [c.receiptClaim!.receiptId])) + .toEqual([{ job_kind: kind, outcome: "deferred" }]); + const d = await start({ installationId: 101, sourceInstallationId: 102, sourceOrgId: 103, + githubRepoId: 104, repoFullName: "fixture/repository", prNumber: 1 }, kind); + expect(await cancelPullRequestPublication(pool, { installationId: 101, sourceInstallationId: 102, + sourceOrgId: 103, githubRepoId: 104, repoFullName: "fixture/repository", prNumber: 1 })).toBe(1); + expect(await rows("SELECT job_kind,outcome FROM job_attempt_receipts WHERE claim_receipt_id=$1 AND event_kind='queue_closed'", [d.receiptClaim!.receiptId])) + .toEqual([{ job_kind: kind, outcome: "cancelled" }]); + const unavailable = await start({}, kind); + const review = await claimPausedHostedReview(database, { ...values(), authorGithubId: null, authorLogin: null, + sinceSha: null, triggerSource: "unknown", triggerContext: { source: "unknown" }, queuedAt: new Date(), startedAt: new Date() }, + { installationId: 1, repoFullName: "fixture/repository", reviewClaim: unavailable }); + expect(review).not.toBeNull(); + expect(await rows("SELECT DISTINCT job_kind FROM job_attempt_receipts WHERE review_id=$1", [review!.id])) + .toEqual([{ job_kind: kind }]); + expect(await completeJob(pool, unavailable)).toBe("done"); + } + }); + + test("existing unavailable-review path atomically binds its finite failure observation", async () => { + const a = await start(); + const r = await claimPausedHostedReview(database, { ...values(), authorGithubId: null, authorLogin: null, + sinceSha: null, triggerSource: "unknown", triggerContext: { source: "unknown" }, queuedAt: new Date(), startedAt: new Date() }, + { installationId: 1, repoFullName: "fixture/repository", reviewClaim: a }); + expect(r).not.toBeNull(); + expect((await rows("SELECT event_kind,outcome FROM job_attempt_receipts WHERE review_id=$1 ORDER BY receipt_id", [r!.id])) + .map((r) => [r.event_kind, r.outcome])).toEqual([["review_bound", "bound"], ["review_observed", "failed"]]); + expect(await completeJob(pool, a)).toBe("done"); + }); + + test("actual reader admission rejects a different target and stale ACK across a cut", async () => { + const [genesis] = await rows("SELECT source_epoch::text FROM job_attempt_receipts WHERE receipt_id=0"); + let state: ReceiptAdmissionState = { revision: 1, binding: { target: randomUUID(), epoch: genesis.source_epoch, schema: 1 }, prefix: "0" }; + const target = state.binding!.target; + const read = () => readAdmittedReceiptPage(state, async (text, values) => ({ target, rows: await rows(text, values) })); + const proposal = await read(); + expect(proposal.status).toBe("READY"); + if (proposal.status !== "READY") throw new Error("fixture page unavailable"); + expect(acknowledgeReceiptPage(state, proposal.page, "9223372036854775807")).toBeNull(); + expect(acknowledgeReceiptPage(state, proposal.page, proposal.page.through)).toBeNull(); + const reported = recordReceiptPageReported(state, proposal.page, proposal.page.rows[0]!.receipt_id as string)!; + const accepted = acknowledgeReceiptPage(reported, proposal.page, reported.reportedThrough!)!; + expect(accepted.prefix).not.toBe("0"); + state = closeReceiptAdmission(accepted); + expect(state.prefix).toBe(accepted.prefix); + expect(acknowledgeReceiptPage(state, proposal.page, proposal.page.through)).toBeNull(); + expect(admitReceiptSource(state, proposal.page.binding, state.revision, "0")).toBeNull(); + const distinct = { ...proposal.page.binding, target: randomUUID() }; + state = admitReceiptSource(state, distinct, state.revision, "0")!; + expect((await read()).status).toBe("UNKNOWN"); + const admitted = await readAdmittedReceiptPage(state, async (text, values) => ({ target: distinct.target, rows: await rows(text, values) })); + expect(admitted.status).toBe("READY"); + + }); + test("generated admission/read/delivery/ACK histories never cross a binding or ACK an unreported row", async () => { + const [control] = await rows("SELECT source_epoch::text FROM job_attempt_receipts WHERE receipt_id=0"); + for (let seed = 1; seed <= 16; seed++) { + let random = seed; + const next = () => { random ^= random << 13; random ^= random >>> 17; random ^= random << 5; return random >>> 0; }; + let state: ReceiptAdmissionState = { revision: 1, binding: { target: randomUUID(), epoch: control.source_epoch, schema: 1 }, prefix: "0" }; + const pages: Array>, { status: "READY" }>["page"]> = []; + const delivered = new Map(); + for (let step = 0; step < 24; step++) { + const action = next() % 5; + if (action === 0 && state.binding) { + const target = state.binding.target; + const page = await readAdmittedReceiptPage(state, async (text, values) => ({ target, rows: await rows(text, values) })); + expect(page.status).toBe("READY"); + if (page.status === "READY") pages.push(page.page); + } else if (action === 1 && pages.length) { + const page = pages[next() % pages.length]!; + const report = recordReceiptPageReported(state, page, page.through); + if (report) { + expect(receiptPageAdmitted(state, page)).toBe(true); + delivered.set(`${page.binding.target}:${page.revision}:${page.after}`, page.through); + state = report; + } + } else if (action === 2 && pages.length) { + const page = pages[next() % pages.length]!; + const acknowledged = acknowledgeReceiptPage(state, page, page.through); + if (acknowledged) { + expect(delivered.get(`${page.binding.target}:${page.revision}:${page.after}`)).toBe(page.through); + expect(acknowledged.binding).toEqual(state.binding); + expect(BigInt(acknowledged.prefix) >= BigInt(state.prefix)).toBe(true); + state = acknowledged; + } + } else if (action === 3) { + const prefix = state.prefix; + state = closeReceiptAdmission(state); + expect(state.prefix).toBe(prefix); + for (const page of pages) expect(acknowledgeReceiptPage(state, page, page.through)).toBeNull(); + } else if (!state.binding) { + const binding = { target: randomUUID(), epoch: control.source_epoch, schema: 1 }; + const revision = state.revision; + state = admitReceiptSource(state, binding, revision, "0")!; + expect(admitReceiptSource(state, binding, revision, "0")).toEqual(state); + } + } + } + }); + +}); diff --git a/tests/private-worker-gates.test.ts b/tests/private-worker-gates.test.ts index e8d4f527..06ebeedd 100644 --- a/tests/private-worker-gates.test.ts +++ b/tests/private-worker-gates.test.ts @@ -17,9 +17,36 @@ describe("private repository worker defense in depth", () => { expect(authorLookup).toBeGreaterThan(gate); expect(source).toContain("authorGithubId: authorGithubId"); expect(source).toContain("authorLogin: authorLogin"); + const reviewInsertion = source.indexOf("await insertReviewForClaim", gate); + expect(reviewInsertion).toBeGreaterThan(gate); + expect( + source.slice(reviewInsertion, source.indexOf("const reviewId", reviewInsertion)), + ).toContain('db, { ...reviewValues, status: "running" }, timing.lease'); + const completion = readFileSync("src/lib/review-completion.ts", "utf8"); + const insertion = completion.slice( + completion.indexOf("export async function insertReviewForClaim"), + completion.indexOf("export async function bindStagedReviewClaim"), + ); + expect(insertion).toContain("if (!lease?.receiptClaim)"); + expect(insertion).toContain("db.insert(schema.reviews).values(values)"); + const transaction = insertion.indexOf("db.transaction"); + const ownership = insertion.indexOf("if (!job?.owned) return undefined;"); + const fencedInsert = insertion.indexOf("tx.insert(schema.reviews)"); + expect(transaction).toBeGreaterThan(0); + expect(ownership).toBeGreaterThan(transaction); + expect(fencedInsert).toBeGreaterThan(ownership); + expect(insertion.slice(transaction, ownership)).toContain( + "await lockReceiptClaim(client, lease)", + ); + expect(insertion.slice(fencedInsert)).toContain( + "originClaimReceiptId: lease.receiptClaim!.receiptId", + ); + expect(insertion.indexOf("await appendPreparedReceiptBatch")).toBeGreaterThan( + fencedInsert, + ); for (const sideEffect of [ - "insert(schema.reviews)", - "await getInstallationToken", + "await insertReviewForClaim", + "getInstallationToken(", "await createCheckRun", "await materializeRepoConfig", "await runCli", @@ -49,10 +76,10 @@ describe("private repository worker defense in depth", () => { source.slice(providerFailure, source.indexOf("await runCli", gate)), ).toContain("configured provider mode does not match"); expect(source.indexOf("fetchRepositorySummary", gate)).toBeLessThan( - source.indexOf("insert(schema.reviews)", gate), + reviewInsertion, ); expect(authorLookup).toBeLessThan( - source.indexOf("insert(schema.reviews)", gate), + reviewInsertion, ); expect(authorLookup).toBeLessThan( source.indexOf("await reserveHostedReviewSpend", gate), @@ -503,12 +530,25 @@ describe("private repository worker defense in depth", () => { ); expect(stagedBranch).not.toContain('kind: "check-run-cleanup"'); expect(stagedBranch).toContain("ReviewPublicationReconciliationError"); - const shutdownBranch = failureBody.slice( - failureBody.indexOf("if (err instanceof WorkerShutdownError) {"), - failureBody.indexOf("const publicationIncomplete"), + const shutdownStart = failureBody.indexOf( + "if (err instanceof WorkerShutdownError) {", ); + const failureTransition = failureBody.indexOf( + "publicationIncomplete = err instanceof CheckRunPublicationError;", + shutdownStart, + ); + expect(shutdownStart).toBeGreaterThan(0); + expect(failureTransition).toBeGreaterThan(shutdownStart); + const shutdownBranch = failureBody.slice(shutdownStart, failureTransition); expect(shutdownBranch).not.toContain('kind: "check-run-cleanup"'); expect(shutdownBranch).toContain('status: "stale"'); + expect(shutdownBranch.indexOf("await reconcileInterruptedSpend()")).toBeGreaterThan( + 0, + ); + expect(shutdownBranch.indexOf("if (!spendError)")).toBeGreaterThan( + shutdownBranch.indexOf("await reconcileInterruptedSpend()"), + ); + expect(shutdownBranch).toContain("throw err;"); expect(failureBody).toContain("db.transaction"); expect(failureBody).toContain('kind: "check-run-cleanup"'); expect(failureBody).toContain('intent: "fail"'); @@ -565,16 +605,31 @@ describe("private repository worker defense in depth", () => { source.indexOf("export async function runReviewJob"), ), ); - const failureUpdate = source.indexOf("const failedRows", catchStart); - const supersessionRace = source.slice(catchStart, failureUpdate); + const stagedCompletion = source.indexOf("if (completionStaged)", catchStart); + expect(catchStart).toBeGreaterThan(0); + expect(stagedCompletion).toBeGreaterThan(catchStart); + const supersessionRace = source.slice(catchStart, stagedCompletion); expect(supersessionRace).toContain( - "err instanceof CheckRunPublicationError && receiptUsageForRace", - ); + "err instanceof CheckRunPublicationError && trustedReceiptUsage", + ); + const ingestion = source.indexOf("const ingested = ingestCompletedHostedReview("); + const usage = source.indexOf( + "const receiptUsage = reviewUsageFromEnvelope(ingested.envelope", + ingestion, + ); + const trustedUsage = source.indexOf("trustedReceiptUsage = receiptUsage;", usage); + expect(ingestion).toBeGreaterThan(0); + expect(usage).toBeGreaterThan(ingestion); + expect(trustedUsage).toBeGreaterThan(usage); + expect(trustedUsage).toBeLessThan(catchStart); expect(supersessionRace).toContain('terminal?.status === "stale"'); expect(supersessionRace).toContain( "await reconcileHostedReviewSpendFromReceipt", ); + expect(supersessionRace).toContain("reservationId: hostedUsageReservationId"); + expect(supersessionRace).toContain("usage: trustedReceiptUsage"); + expect(supersessionRace).toContain("usageAccountingComplete: trustedUsageAccountingComplete"); expect(supersessionRace).toContain("await neutralizeSupersededCheckRuns"); expect(supersessionRace).toContain("return;"); }); diff --git a/tests/worker-runner.test.ts b/tests/worker-runner.test.ts index 7863aeba..16a3ebfc 100644 --- a/tests/worker-runner.test.ts +++ b/tests/worker-runner.test.ts @@ -1214,7 +1214,9 @@ describe("drainQueueOnce", () => { }); test("processes no more than maxJobs", async () => { - jobs.push(reviewJob(1), reviewJob(2)); + const first = reviewJob(1); + first.receiptClaim = Object.freeze({ jobId: "1", receiptId: String(Date.now()) }); + jobs.push(first, reviewJob(2)); const drained = await drainQueueOnce("test-drain", { maxJobs: 1, @@ -1227,7 +1229,7 @@ describe("drainQueueOnce", () => { expect(reviewTiming).toEqual({ queuedAt: new Date("2026-07-10T12:00:00.000Z"), startedAt: new Date("2026-07-10T12:00:05.000Z"), - lease: expect.objectContaining({ id: 1, lockedBy: "test" }), + lease: expect.objectContaining({ id: 1, lockedBy: "test", receiptClaim: first.receiptClaim }), }); expect(reviewProcessGroup).toBe("web"); });