Merge qualified backup expiration parent into GitHub connection work

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* commit 'aae32f2f493848199f6cb8c0a9996af3ee0061b1': (30 commits)
  Scope status-card refresh tasks to their claim revision
  test(chat): retain completion identity before link metadata loads
  test(chat): locate completion through its rendered task link
  fix(chat): recover committed native Agent Chat replies (#18465)
  perf(heartbeat): drive retry promotion from durable deadlines (#18387)
  fix(ui): make the Coordinator composer footer opaque (#18455)
  fix(testops): verify unexpanded result job runs (#18462)
  test(server): observe restored Retry before draining unrelated work
  test(runner): own listener cleanup across setup failures
  test(runner): buffer upgrade frames until the wire subscribes
  fix(runner): preserve OpenCode written-answer defaults (#18456)
  fix(native): fence retries when replaying an audited Stop (#18457)
  test: await async outcomes and drive saved deadlines deterministically (#18416)
  fix(ui): retain startup autosave through session recovery (#18412)
  perf(server): recover independent native replies concurrently (#18421)
  perf(chat): scope task questions and batch inbox queue checks (#18422)
  fix(native): recover controllers across webserver replacement (#18343)
  fix(ui): restore task document authoring and add persistence journey (#18356)
  fix(tools): stabilize tied profile entry order (#18427)
  fix(adapter-utils): tolerate entries removed during workspace snapshots (#18426)
  ...
This commit is contained in:
DottaandPaperclip committed 2026-10-31 09:41:51 -05:00
commit f522ab07e2
484 files changed
+33834 -1620

No files matched your search

@@ -0,0 +1,63 @@
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import postgres from "postgres";
import { describe, expect, it } from "vitest";
import { applyPendingMigrations } from "./client.js";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./test-embedded-postgres.js";
const support = await getEmbeddedPostgresTestSupport();
const indexName = "issue_comments_agent_client_request_uq";
const migrationFile = "0419_slippery_impossible_man.sql";
(support.supported ? describe : describe.skip)("agent chat message migration", () => {
it("repairs an interrupted index build and backfills old chat owners only once", async () => {
const database = await startEmbeddedPostgresTestDatabase("agent-chat-message-migration-");
const sql = postgres(database.connectionString, { max: 1, onnotice: () => {} });
try {
const content = await readFile(new URL(`./migrations/${migrationFile}`, import.meta.url), "utf8");
const hash = createHash("sha256").update(content).digest("hex");
await sql`DELETE FROM drizzle.__drizzle_migrations WHERE hash=${hash}`;
await sql.unsafe(`ALTER TABLE issue_comments DROP CONSTRAINT ${indexName}`);
const companies = await sql`INSERT INTO companies (name,issue_prefix) VALUES ('Chat migration','CMI1'),('Other company','CMI2') RETURNING id`;
const companyId = companies[0]!.id;
const [agent] = await sql`INSERT INTO agents (company_id,name,adapter_type) VALUES (${companyId},'Kodi','process') RETURNING id`;
const chats = await sql`INSERT INTO issues (company_id,title,conversation_agent_id,conversation_user_id,conversation_state,assignee_agent_id)
VALUES (${companyId},'Missing read state',${agent!.id},'legacy-owner','waiting',${agent!.id}),
(${companyId},'Existing read state',${agent!.id},'existing-owner','waiting',${agent!.id}) RETURNING id,conversation_user_id`;
for (const chat of chats) {
await sql`INSERT INTO issue_comments (company_id,issue_id,author_agent_id,body,created_at)
VALUES (${companyId},${chat.id},${agent!.id},'Existing message','2026-01-02T00:00:00Z')`;
}
await sql`INSERT INTO issue_read_states (company_id,issue_id,user_id,last_read_at)
VALUES (${companyId},${chats[1]!.id},'existing-owner','2026-01-01T00:00:00Z')`;
await expect(sql.unsafe(`CREATE UNIQUE INDEX CONCURRENTLY ${indexName} ON companies ((true))`)).rejects.toThrow();
await Promise.all([applyPendingMigrations(database.connectionString), applyPendingMigrations(database.connectionString)]);
const [index] = await sql`SELECT indisvalid,indisunique FROM pg_index WHERE indexrelid=${indexName}::regclass`;
expect(index).toEqual({ indisvalid: true, indisunique: true });
const [constraint] = await sql`SELECT count(*)::int count FROM pg_constraint WHERE conname=${indexName} AND conrelid='issue_comments'::regclass`;
expect(constraint!.count).toBe(1);
const readStates = await sql`SELECT user_id,last_read_at FROM issue_read_states WHERE company_id=${companyId} ORDER BY user_id`;
expect(readStates.map(row => [row.user_id, row.last_read_at.toISOString()])).toEqual([
["existing-owner", "2026-01-01T00:00:00.000Z"],
["legacy-owner", "2026-01-02T00:00:00.000Z"],
]);
const [newChat] = await sql`INSERT INTO issues (company_id,title,conversation_agent_id,conversation_user_id,conversation_state,assignee_agent_id)
VALUES (${companyId},'New unread chat',${agent!.id},'new-owner','waiting',${agent!.id}) RETURNING id`;
await sql`INSERT INTO issue_comments (company_id,issue_id,author_agent_id,body)
VALUES (${companyId},${newChat!.id},${agent!.id},'New digest')`;
for (let replay = 0; replay < 2; replay++) {
for (const statement of content.split("--> statement-breakpoint").filter(statement => statement.trim())) {
await sql.unsafe(statement);
}
}
expect(await sql`SELECT id FROM issue_read_states WHERE issue_id=${newChat!.id}`).toEqual([]);
const afterReplay = await sql`SELECT user_id,last_read_at FROM issue_read_states WHERE company_id=${companyId} ORDER BY user_id`;
expect(afterReplay).toEqual(readStates);
const [history] = await sql`SELECT count(*)::int count FROM drizzle.__drizzle_migrations WHERE hash=${hash}`;
expect(history!.count).toBe(1);
} finally {
await sql.end();
await database.cleanup();
}
}, 120_000);
});
+15 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, expect, it } from "vitest";
import { checkpointDb, closeCheckpointDb, closeRegisteredClients, createDb, type Db } from "./client.js";
import { checkpointDb, closeCheckpointDb, closeRegisteredClients, createDb, livenessDb, type Db } from "./client.js";
// postgres.js pools are lazy: these tests exercise actual client lifetimes but
// never connect to a database or depend on embedded Postgres availability.
@@ -17,6 +17,7 @@ it("fences checkpoints when shutdown precedes the first pool request", async ()
it("keeps an already-created checkpoint pool ended after shutdown", async () => {
const db = createDb(url);
const pool = checkpointDb(db);
expect(pool.$client.options.max).toBe(3);
await closeCheckpointDb(db, 1);
expect(checkpointDb(db)).toBe(pool);
await expect(pool.$client`select 1`).rejects.toMatchObject({ code: "CONNECTION_ENDED" });
@@ -30,3 +31,16 @@ it("preserves other owner lifetimes and non-owning test-double behavior", async
await closeCheckpointDb(nonOwner, 1);
expect(checkpointDb(nonOwner)).toBe(nonOwner);
});
it("bounds one cached checkpoint pool per owner without changing request or renewal capacity", () => {
const first = createDb(url, { maxConnections: 10 });
const second = createDb(url, { maxConnections: 10 });
const pool = checkpointDb(first);
expect(pool).not.toBe(first);
expect(checkpointDb(first)).toBe(pool);
expect(checkpointDb(second)).not.toBe(pool);
expect(pool.$client.options.max).toBe(3);
expect(checkpointDb(second).$client.options.max).toBe(3);
expect(first.$client.options.max).toBe(10);
expect(livenessDb(first).$client.options.max).toBe(2);
});
+4 -2
View File
@@ -295,9 +295,10 @@ export async function closeLivenessDb(db: Db, timeoutSeconds: number): Promise<v
}
// Control checkpoints must not occupy the request or lease-renewal pools
// during an aligned burst. Share at most two lazy connections per owner.
// during an aligned burst. Share at most three lazy connections per owner.
const checkpointDbFactories = new WeakMap<object, () => Db>();
const checkpointDbs = new WeakMap<object, Db>();
const CHECKPOINT_POOL_MAX_CONNECTIONS = 3;
const closedCheckpointOwners = new WeakSet<object>();
/** The bounded controller checkpoint pool; transactions and test doubles keep
@@ -342,7 +343,7 @@ function createDbClient(url: string, resolved: DatabaseClientOptions, owner?: ob
...resolved, maxConnections: LIVENESS_POOL_MAX_CONNECTIONS, applicationName: "paperclip-controller-lease",
}, db));
checkpointDbFactories.set(db, () => createDbClient(url, {
...resolved, maxConnections: 2, applicationName: "paperclip-runner-checkpoint",
...resolved, maxConnections: CHECKPOINT_POOL_MAX_CONNECTIONS, applicationName: "paperclip-runner-checkpoint",
}, db));
return db;
}
@@ -564,6 +565,7 @@ const MIGRATIONS_WITH_TOP_LEVEL_STATEMENTS = new Set([
"0412_tearful_squadron_supreme.sql",
"0413_tired_kronos.sql",
"0414_closed_edwin_jarvis.sql",
"0419_slippery_impossible_man.sql",
"0384_funny_adam_warlock.sql",
@@ -0,0 +1,24 @@
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "issue_comments_agent_client_request_uq"
ON "issue_comments" ("issue_id", "author_agent_id", "client_request_id");
--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'issue_comments_agent_client_request_uq'
AND conrelid = 'issue_comments'::regclass
) THEN
INSERT INTO "issue_read_states" ("company_id", "issue_id", "user_id", "last_read_at")
SELECT i."company_id", i."id", i."conversation_user_id", max(c."created_at")
FROM "issues" i
JOIN "issue_comments" c ON c."company_id" = i."company_id" AND c."issue_id" = i."id"
WHERE i."conversation_agent_id" IS NOT NULL AND i."conversation_user_id" IS NOT NULL
AND c."deleted_at" IS NULL AND c."author_agent_id" IS NOT NULL
AND c."author_type" IS DISTINCT FROM 'system'
AND c."presentation" ->> 'kind' IS DISTINCT FROM 'system_notice'
AND c."origin" = 'comment'
GROUP BY i."company_id", i."id", i."conversation_user_id"
ON CONFLICT ("company_id", "issue_id", "user_id") DO NOTHING;
ALTER TABLE "issue_comments" ADD CONSTRAINT "issue_comments_agent_client_request_uq"
UNIQUE USING INDEX "issue_comments_agent_client_request_uq";
END IF;
END $$;
+10 -7
View File
@@ -1,5 +1,5 @@
{
"id": "cce00822-b7a4-4acd-9c59-0f6f5822c0cf",
"id": "52cf22f3-3bd9-4b0f-8d23-b422bb0d2540",
"prevId": "c777fa84-85a6-418f-bea1-0d60b56cf071",
"version": "7",
"dialect": "postgresql",
@@ -37105,6 +37105,15 @@
"client_request_id"
]
},
"issue_comments_agent_client_request_uq": {
"name": "issue_comments_agent_client_request_uq",
"nullsNotDistinct": false,
"columns": [
"issue_id",
"author_agent_id",
"client_request_id"
]
},
"issue_comments_company_id_uq": {
"name": "issue_comments_company_id_uq",
"nullsNotDistinct": false,
@@ -52770,12 +52779,6 @@
"primaryKey": false,
"notNull": false
},
"next_eval_reason": {
"name": "next_eval_reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"next_eval_at": {
"name": "next_eval_at",
"type": "timestamp with time zone",
@@ -1,6 +1,6 @@
{
"id": "dce41524-29a3-4552-ba5d-f0abefbf1ea1",
"prevId": "27c9008c-60e3-449b-9630-9e4977fad444",
"id": "8f75a1bb-32d9-4aed-9f93-134a6ae71f79",
"prevId": "52cf22f3-3bd9-4b0f-8d23-b422bb0d2540",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -19052,6 +19052,98 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.company_skill_mutation_receipts": {
"name": "company_skill_mutation_receipts",
"schema": "",
"columns": {
"company_id": {
"name": "company_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"skill_id": {
"name": "skill_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"version_id": {
"name": "version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"response": {
"name": "response",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"company_skill_mutation_receipts_key_idx": {
"name": "company_skill_mutation_receipts_key_idx",
"columns": [
{
"expression": "company_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"company_skill_mutation_receipts_company_id_companies_id_fk": {
"name": "company_skill_mutation_receipts_company_id_companies_id_fk",
"tableFrom": "company_skill_mutation_receipts",
"tableTo": "companies",
"columnsFrom": [
"company_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.company_skill_repository_snapshots": {
"name": "company_skill_repository_snapshots",
"schema": "",
@@ -34070,7 +34162,7 @@
}
],
"isUnique": false,
"where": "\"heartbeat_runs\".\"runtime_mode\" = 'native' AND \"heartbeat_runs\".\"status\" = 'cancelled'\n AND \"heartbeat_runs\".\"runner_profile_json\"->'nativeWorkspaceSync'->>'state' = 'prepared'\n AND \"heartbeat_runs\".\"result_json\"->'nativeCancellation'->>'dispatchState' = 'acknowledged'\n AND (\"heartbeat_runs\".\"result_json\"->'workspaceRestoreRecovery'->>'schema' = 'paperclip.workspace-restore-recovery.v1'\n AND \"heartbeat_runs\".\"result_json\"->>'workspaceRestoreFailure' IN ('restore_permission_denied', 'restore_lock_timeout', 'restore_unsafe_archive', 'restore_failed')) IS NOT TRUE",
"where": "\"heartbeat_runs\".\"runtime_mode\" = 'native' AND \"heartbeat_runs\".\"status\" = 'cancelled'\n AND \"heartbeat_runs\".\"runner_profile_json\"->'nativeWorkspaceSync'->>'state' = 'prepared'\n AND \"heartbeat_runs\".\"result_json\"->'nativeCancellation'->>'dispatchState' = 'acknowledged'\n AND (\"heartbeat_runs\".\"result_json\"->'workspaceRestoreRecovery'->>'schema' = 'paperclip.workspace-restore-recovery.v1'\n AND \"heartbeat_runs\".\"result_json\"->>'workspaceRestoreFailure' IN ('restore_permission_denied', 'restore_lock_timeout', 'restore_unsafe_archive', 'restore_invalid_metadata', 'restore_failed')) IS NOT TRUE",
"concurrently": false,
"method": "btree",
"with": {}
@@ -37013,6 +37105,15 @@
"client_request_id"
]
},
"issue_comments_agent_client_request_uq": {
"name": "issue_comments_agent_client_request_uq",
"nullsNotDistinct": false,
"columns": [
"issue_id",
"author_agent_id",
"client_request_id"
]
},
"issue_comments_company_id_uq": {
"name": "issue_comments_company_id_uq",
"nullsNotDistinct": false,
@@ -51482,6 +51583,195 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sandbox_creation_operations": {
"name": "sandbox_creation_operations",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"company_id": {
"name": "company_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"environment_id": {
"name": "environment_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"original_environment_id": {
"name": "original_environment_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"request_key": {
"name": "request_key",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'run'"
},
"issue_id": {
"name": "issue_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"execution_workspace_id": {
"name": "execution_workspace_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"plugin_id": {
"name": "plugin_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"driver_key": {
"name": "driver_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"state": {
"name": "state",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"config": {
"name": "config",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"lease_id": {
"name": "lease_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"next_attempt_at": {
"name": "next_attempt_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": {
"sandbox_creation_operations_pending_deadline_idx": {
"name": "sandbox_creation_operations_pending_deadline_idx",
"columns": [
{
"expression": "next_attempt_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"where": "\"sandbox_creation_operations\".\"state\" = 'pending'",
"concurrently": false,
"method": "btree",
"with": {}
},
"sandbox_creation_operations_owner_idx": {
"name": "sandbox_creation_operations_owner_idx",
"columns": [
{
"expression": "company_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "original_environment_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "run_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "request_key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"sandbox_creation_operations_environment_id_environments_id_fk": {
"name": "sandbox_creation_operations_environment_id_environments_id_fk",
"tableFrom": "sandbox_creation_operations",
"tableTo": "environments",
"columnsFrom": [
"environment_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.secret_access_events": {
"name": "secret_access_events",
"schema": "",
@@ -52489,6 +52779,12 @@
"primaryKey": false,
"notNull": false
},
"next_eval_reason": {
"name": "next_eval_reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"next_eval_at": {
"name": "next_eval_at",
"type": "timestamp with time zone",
+9 -2
View File
@@ -2924,8 +2924,15 @@
{
"idx": 419,
"version": "7",
"when": 1793402408013,
"tag": "0419_moaning_vulture",
"when": 1793400383797,
"tag": "0419_slippery_impossible_man",
"breakpoints": true
},
{
"idx": 420,
"version": "7",
"when": 1793453735600,
"tag": "0420_broad_johnny_storm",
"breakpoints": true
}
]
+1
View File
@@ -49,6 +49,7 @@ export const issueComments = pgTable(
},
(table) => ({
clientRequestUq: unique("issue_comments_client_request_uq").on(table.issueId, table.authorUserId, table.clientRequestId),
agentClientRequestUq: unique("issue_comments_agent_client_request_uq").on(table.issueId, table.authorAgentId, table.clientRequestId),
companyIdUq: unique("issue_comments_company_id_uq").on(table.companyId, table.id),
issueIdx: index("issue_comments_issue_idx").on(table.issueId),
reviewResponseReceiptIdx: index("issue_comments_review_response_receipt_idx").on(
@@ -0,0 +1,111 @@
import { createHash, randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";
import postgres from "postgres";
import { describe, expect, it } from "vitest";
import { applyPendingMigrations, inspectMigrations } from "./client.js";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./test-embedded-postgres.js";
const adminUrl = process.env.PAPERCLIP_TEST_POSTGRES_ADMIN_URL;
const support = adminUrl ? { supported: true } : await getEmbeddedPostgresTestSupport();
const journal = JSON.parse(await readFile(new URL("./migrations/meta/_journal.json", import.meta.url), "utf8")) as {
entries: Array<{ idx: number; tag: string }>;
};
const master = journal.entries.find(entry => entry.tag === "0419_slippery_impossible_man")!;
const status = journal.entries.find(entry => entry.tag === "0420_broad_johnny_storm")!;
const statusSql = await readFile(new URL(`./migrations/${status.tag}.sql`, import.meta.url), "utf8");
const hash = async (tag: string) => createHash("sha256")
.update(await readFile(new URL(`./migrations/${tag}.sql`, import.meta.url), "utf8")).digest("hex");
async function database() {
if (!adminUrl) return startEmbeddedPostgresTestDatabase("status-reason-upgrade-");
const name = `status_reason_${randomUUID().replaceAll("-", "")}_test`;
const admin = postgres(adminUrl, { max: 1, onnotice: () => {} });
try { await admin.unsafe(`create database "${name}"`); }
finally { await admin.end(); }
const url = new URL(adminUrl); url.pathname = `/${name}`;
const cleanup = async () => {
const admin = postgres(adminUrl, { max: 1, onnotice: () => {} });
try { await admin.unsafe(`drop database "${name}"`); }
finally { await admin.end(); }
};
try { await applyPendingMigrations(url.toString()); }
catch (error) { await cleanup(); throw error; }
return { connectionString: url.toString(), cleanup };
}
async function seed(sql: postgres.Sql) {
const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), userId = randomUUID();
const lastMessage = new Date("2026-10-20T12:00:00.000Z");
await sql`insert into companies (id, name, issue_prefix) values (${companyId}, 'Migration proof', ${`M${companyId.slice(0, 10)}`})`;
await sql`insert into agents (id, company_id, name, adapter_type) values (${agentId}, ${companyId}, 'Agent', 'process')`;
await sql`insert into "user" (id, name, email, email_verified, created_at, updated_at)
values (${userId}, 'Reader', ${`${userId}@example.test`}, true, now(), now())`;
await sql`insert into issues (id, company_id, title, status, assignee_agent_id,
conversation_agent_id, conversation_user_id, conversation_state)
values (${issueId}, ${companyId}, 'Conversation', 'in_review', ${agentId}, ${agentId}, ${userId}, 'waiting')`;
await sql`insert into issue_comments (company_id, issue_id, author_agent_id, author_type, body, origin, client_request_id, created_at)
values (${companyId}, ${issueId}, ${agentId}, 'agent', 'Preserved reply', 'comment', 'retry-key', ${lastMessage})`;
await sql`insert into issue_comments (company_id, issue_id, author_agent_id, author_type, body, origin, created_at)
values (${companyId}, ${issueId}, ${agentId}, 'system', 'Excluded system notice', 'comment', ${new Date(lastMessage.getTime() + 1000)})`;
const cardId = randomUUID(), savedDeadline = new Date("2026-10-21T12:00:00.000Z");
await sql`insert into status_cards (id, company_id, interest_prompt, refresh_policy, next_eval_at, pending_change_hash)
values (${cardId}, ${companyId}, 'Watch preserved work', '{"mode":"manual"}'::jsonb, ${savedDeadline}, 'preserved-change')`;
return { companyId, agentId, issueId, userId, lastMessage, cardId, savedDeadline };
}
async function assertFinal(sql: postgres.Sql, row: Awaited<ReturnType<typeof seed>>, expectedRead: Date) {
const [column] = await sql`select data_type, is_nullable from information_schema.columns
where table_schema = 'public' and table_name = 'status_cards' and column_name = 'next_eval_reason'`;
expect(column).toMatchObject({ data_type: "text", is_nullable: "YES" });
const [constraint] = await sql`select i.indisvalid, i.indisunique from pg_constraint c
join pg_index i on i.indexrelid = c.conindid
where c.conrelid = 'issue_comments'::regclass and c.conname = 'issue_comments_agent_client_request_uq'`;
expect(constraint).toMatchObject({ indisvalid: true, indisunique: true });
const [read] = await sql`select last_read_at from issue_read_states
where company_id = ${row.companyId} and issue_id = ${row.issueId} and user_id = ${row.userId}`;
expect(read.last_read_at).toEqual(expectedRead);
const [card] = await sql`select next_eval_at, next_eval_reason, pending_change_hash from status_cards where id = ${row.cardId}`;
expect(card).toMatchObject({ next_eval_at: row.savedDeadline, next_eval_reason: null, pending_change_hash: "preserved-change" });
await sql`update status_cards set next_eval_reason = 'retry' where id = ${row.cardId}`;
expect((await sql`select next_eval_at, next_eval_reason from status_cards where id = ${row.cardId}`)[0])
.toMatchObject({ next_eval_at: row.savedDeadline, next_eval_reason: "retry" });
await expect(sql`insert into issue_comments (company_id, issue_id, author_agent_id, body, client_request_id)
values (${row.companyId}, ${row.issueId}, ${row.agentId}, 'Duplicate', 'retry-key')`)
.rejects.toMatchObject({ code: "23505", constraint_name: "issue_comments_agent_client_request_uq" });
}
describe.skipIf(!support.supported)("status-card reason migration after the published comment migration", () => {
it.each(["0418", "master0419"] as const)("upgrades from %s without dropping comment uniqueness or read history", async previous => {
expect(master.idx).toBe(419);
expect(status.idx).toBeGreaterThan(master.idx);
expect(statusSql.trim()).toBe('ALTER TABLE "status_cards" ADD COLUMN "next_eval_reason" text;');
const db = await database();
const sql = postgres(db.connectionString, { max: 1, onnotice: () => {} });
try {
// Reconstruct exactly the two migrations' preimages; all older schema and
// receipts stay intact, as in the repository's other replay regressions.
await sql`alter table status_cards drop column next_eval_reason`;
await sql`delete from drizzle.__drizzle_migrations where hash = ${await hash(status.tag)}`;
if (previous === "0418") {
await sql`alter table issue_comments drop constraint issue_comments_agent_client_request_uq`;
await sql`delete from drizzle.__drizzle_migrations where hash = ${await hash(master.tag)}`;
}
const row = await seed(sql);
const preservedRead = new Date("2026-10-19T12:00:00.000Z");
if (previous === "master0419") await sql`insert into issue_read_states (company_id, issue_id, user_id, last_read_at)
values (${row.companyId}, ${row.issueId}, ${row.userId}, ${preservedRead})`;
expect(await inspectMigrations(db.connectionString)).toMatchObject({
status: "needsMigrations",
pendingMigrations: previous === "0418" ? [`${master.tag}.sql`, `${status.tag}.sql`] : [`${status.tag}.sql`],
});
await applyPendingMigrations(db.connectionString);
expect(await inspectMigrations(db.connectionString)).toMatchObject({ status: "upToDate" });
await assertFinal(sql, row, previous === "0418" ? row.lastMessage : preservedRead);
const receipts = await sql`select hash from drizzle.__drizzle_migrations order by id`;
const hashes = receipts.map(receipt => receipt.hash);
expect(hashes.indexOf(await hash(master.tag))).toBeLessThan(hashes.indexOf(await hash(status.tag)));
await applyPendingMigrations(db.connectionString);
expect(await sql`select hash from drizzle.__drizzle_migrations order by id`).toEqual(receipts);
} finally { await sql.end(); await db.cleanup(); }
}, 90_000);
});