refactor(outbound): remove beta queue reply migration (#127133)

* refactor(outbound): remove beta queue reply migration

* test(outbound): retain legacy reply migration coverage

* test(outbound): type legacy migration fixture
This commit is contained in:
Peter Steinberger
2026-08-21 02:54:42 -07:00
committed by GitHub
parent 225aa5a178
commit 81628db1d7
2 changed files with 15 additions and 143 deletions
@@ -26,6 +26,7 @@ import {
markDeliveryPlatformOutcomeUnknown,
markDeliveryPlatformSendAttemptStarted,
reserveDeliveryAttempt,
type LegacyQueuedDelivery,
type LegacyQueuedDeliveryPreparation,
type QueuedDelivery,
} from "./delivery-queue-storage.js";
@@ -144,106 +145,17 @@ describe("outbound prepared queue migration", () => {
);
});
it("persists canonical reply facts across prepared namespaces exactly once", async () => {
const cases = [
{
label: "first",
legacy: { replyToId: "legacy-first", replyToMode: "first" },
expected: { source: "implicit", replyToId: "legacy-first", mode: "first" },
},
{
label: "batched",
legacy: { replyToId: "legacy-batched", replyToMode: "batched" },
expected: { source: "implicit", replyToId: "legacy-batched", mode: "first" },
},
{
label: "all",
legacy: { replyToId: "legacy-all", replyToMode: "all" },
expected: { source: "implicit", replyToId: "legacy-all", mode: "all" },
},
{
label: "off",
legacy: { replyToId: "legacy-off", replyToMode: "off" },
expected: undefined,
},
{
label: "explicit",
legacy: {
reply: { source: "explicit", replyToId: "explicit-root" },
replyToId: "legacy-first",
replyToMode: "first",
},
expected: { source: "explicit", replyToId: "explicit-root" },
},
] as const;
const sourceNamespaces = [
OUTBOUND_DELIVERY_QUEUE_NAME,
OUTBOUND_DELIVERY_MIGRATION_QUEUE_NAME,
] as const;
const ids: string[] = [];
for (const sourceQueueName of sourceNamespaces) {
for (const testCase of cases) {
const id = `${sourceQueueName}-${testCase.label}`;
ids.push(id);
const entry = {
id,
enqueuedAt: 100,
retryCount: 0,
attemptCount: 0,
channel: "matrix",
to: "!room:example",
queuePolicy: "required",
preparedBatch: {
schemaVersion: 1,
sourcePayloadCount: 1,
entries: [{ sourceIndex: 0, status: "accepted", payload: { text: id } }],
},
...testCase.legacy,
};
upsertDeliveryQueueEntry({
queueName: sourceQueueName,
entry,
stateDir: tmpDir(),
});
}
}
await migrateLegacyPendingOutboundDeliveries({
cfg: {},
log: createRecoveryLog(),
stateDir: tmpDir(),
});
const firstPass = new Map<string, string>();
for (const id of ids) {
const raw = readQueueEntryJson(OUTBOUND_DELIVERY_QUEUE_NAME, id, tmpDir());
expect(raw).toBeDefined();
firstPass.set(id, raw ?? "");
const persisted = JSON.parse(raw ?? "{}") as Record<string, unknown>;
const testCase = cases.find((candidate) => id.endsWith(`-${candidate.label}`));
expect(persisted.reply).toEqual(testCase?.expected);
expect(persisted).not.toHaveProperty("replyToId");
expect(persisted).not.toHaveProperty("replyToMode");
}
await migrateLegacyPendingOutboundDeliveries({
cfg: {},
log: createRecoveryLog(),
stateDir: tmpDir(),
});
for (const id of ids) {
expect(readQueueEntryJson(OUTBOUND_DELIVERY_QUEUE_NAME, id, tmpDir())).toBe(
firstPass.get(id),
);
}
});
it("prepares a legacy row once, fences rollback, and never reruns modifiers", async () => {
const id = "stable-legacy-delivery";
const source = {
...legacyEntry(id, "secret"),
completionRetention: "permanent",
replyToId: "root-message",
replyToMode: "batched",
} satisfies LegacyQueuedDelivery;
upsertDeliveryQueueEntry({
queueName: LEGACY_OUTBOUND_DELIVERY_QUEUE_NAME,
entry: { ...legacyEntry(id, "secret"), completionRetention: "permanent" },
entry: source,
stateDir: tmpDir(),
});
@@ -269,6 +181,13 @@ describe("outbound prepared queue migration", () => {
expect(
acceptedPreparedOutboundEntries(queued.preparedBatch).map((entry) => entry.payload),
).toEqual([{ text: "secret-prepared" }]);
expect(queued.reply).toEqual({
source: "implicit",
replyToId: "root-message",
mode: "first",
});
expect(queued).not.toHaveProperty("replyToId");
expect(queued).not.toHaveProperty("replyToMode");
expect(queued).not.toHaveProperty("legacyPreparationOwnerId");
expect(queued).not.toHaveProperty("legacyPreparationLeaseExpiresAt");
@@ -10,8 +10,6 @@ import {
} from "../delivery-queue-sqlite-namespace.js";
import {
countPendingDeliveryQueueEntries,
loadDeliveryQueueEntries,
loadDeliveryQueueEntry,
terminalizePendingDeliveryQueueEntry,
} from "../delivery-queue-sqlite.js";
import {
@@ -54,47 +52,6 @@ import { normalizeOutboundReplyFacts } from "./reply-policy.js";
const LEGACY_PREPARATION_LEASE_MS = 5 * 60_000;
const LEGACY_PREPARATION_LEASE_RENEW_MS = 30_000;
type LegacyPreparedQueuedDelivery = QueuedDelivery &
Parameters<typeof normalizeOutboundReplyFacts>[0];
function hasLegacyReplyFields(entry: LegacyPreparedQueuedDelivery): boolean {
return Object.hasOwn(entry, "replyToId") || Object.hasOwn(entry, "replyToMode");
}
function canonicalizePreparedReplyFields(entry: LegacyPreparedQueuedDelivery): QueuedDelivery {
const { replyToId, replyToMode, reply: storedReply, ...canonical } = entry;
const reply = normalizeOutboundReplyFacts({ reply: storedReply, replyToId, replyToMode });
return { ...canonical, ...(reply ? { reply } : {}) };
}
function migratePreparedReplyFields(queueName: string, stateDir?: string): void {
const entries = loadDeliveryQueueEntries(queueName, stateDir) as LegacyPreparedQueuedDelivery[]; // SAFETY: callers pass prepared namespaces; beta rows add only legacy reply fields.
for (const entry of entries) {
if (!hasLegacyReplyFields(entry)) {
continue;
}
const migrated = canonicalizePreparedReplyFields(entry);
if (
replacePendingDeliveryQueueEntry({
queueName,
expectedEntry: entry,
replacementEntry: migrated,
stateDir,
})
) {
continue;
}
const current = loadDeliveryQueueEntry(
queueName,
entry.id,
stateDir,
) as LegacyPreparedQueuedDelivery | null; // SAFETY: same prepared namespace/id; replacement keeps shape or removes row.
if (current && hasLegacyReplyFields(current)) {
throw new Error(`Prepared delivery ${entry.id} changed during reply migration`);
}
}
}
function withLegacyPreparationLease(
entry: LegacyQueuedDeliveryPreparation,
ownerId: string,
@@ -537,10 +494,6 @@ async function migrateLegacyPendingOutboundDeliveriesOwned(params: {
log: RecoveryLogger;
stateDir?: string;
}): Promise<LegacyOutboundDeliveryMigrationResult> {
// Beta rows can exist in either prepared namespace. Canonicalize them before
// any recovery owner sees the row; interrupted migrations then resume normally.
migratePreparedReplyFields(OUTBOUND_DELIVERY_QUEUE_NAME, params.stateDir);
migratePreparedReplyFields(OUTBOUND_DELIVERY_MIGRATION_QUEUE_NAME, params.stateDir);
let moved = 0;
let skipped = 0;
const ownerId = randomUUID();