Harden conversation replies and prioritize newest candidates

This commit is contained in:
jjbrunton committed 2026-08-09 18:16:57 +01:00
1 parent f98c5c732d
commit b8e4a1bb88
23 files changed
+598 -113

No files matched your search

+45 -8
View File
@@ -94,6 +94,42 @@ describe('CandidateQueueRepository', () => {
expect(queue.countActive()).toBe(2);
});
it('admits, screens, and sends the newest posts before older higher-priority posts', () => {
queue.enqueue([
post('old-high-priority', 'alice', 100),
post('new-low-priority', 'bob', 300),
], 'image', 1, 1_000, 1);
expect(queue.listActive().map((candidate) => candidate.postId)).toEqual(['new-low-priority']);
queue.enqueue([post('old-high-priority', 'alice', 100)], 'image', 10, 1_001);
const newestScreening = queue.claimForScreening('screen-new', 100, 1_010)!;
expect(newestScreening.postId).toBe('new-low-priority');
queue.markReady(newestScreening.postId, 'screen-new', prepared(newestScreening.post), 1_011);
const olderScreening = queue.claimForScreening('screen-old', 100, 1_012)!;
queue.markReady(olderScreening.postId, 'screen-old', prepared(olderScreening.post), 1_013);
expect(queue.claimReady('account-a', 'send', 100, 1_014)?.postId).toBe('new-low-priority');
});
it('promotes the newest due delivery retry when ready capacity is limited', () => {
queue.enqueue([post('new-low-priority', 'bob', 300)], 'image', 1, 1_000);
queue.enqueue([post('old-high-priority', 'alice', 100)], 'image', 10, 1_001);
for (const worker of ['screen-new', 'screen-old']) {
const candidate = queue.claimForScreening(worker, 100, 1_010)!;
queue.markReady(candidate.postId, worker, prepared(candidate.post), 1_011);
}
for (const [accountId, worker] of [['account-a', 'send-new'], ['account-b', 'send-old']] as const) {
const candidate = queue.claimReady(accountId, worker, 100, 1_012)!;
queue.deferDelivery(candidate.postId, worker, 'Matrix rate limited', 1_100, 1_013);
}
expect(queue.promoteDueRetries(1, 1_100)).toBe(1);
expect(queue.claimReady('account-a', 'retry', 100, 1_101)?.postId).toBe('new-low-priority');
});
it('excludes posts and recipients already handled anywhere', () => {
db.prepare(`
INSERT INTO processed_posts (account_id, id, subreddit, author, title, processed_at)
@@ -140,13 +176,13 @@ describe('CandidateQueueRepository', () => {
});
});
it('lists only active candidates in actionable stage order', () => {
it('lists only active candidates in newest-post order across stages', () => {
queue.enqueue([
post('waiting', 'alice'),
post('screening', 'bob'),
post('ready', 'carol'),
post('sending', 'dana'),
post('rejected', 'erin'),
post('waiting', 'alice', 100),
post('screening', 'bob', 500),
post('ready', 'carol', 400),
post('sending', 'dana', 300),
post('rejected', 'erin', 200),
], 'image', 1, 1_000);
queue.claimForScreening('worker-1', 1_000, 1_010);
const ready = queue.claimForScreening('worker-2', 1_000, 1_020)!;
@@ -155,8 +191,9 @@ describe('CandidateQueueRepository', () => {
const rejected = queue.claimForScreening('worker-4', 1_000, 1_040)!;
queue.reject(rejected.postId, 'worker-4', 'Seller signals detected', 1_041);
expect(queue.listActive().map((candidate) => candidate.status)).toEqual([
'sending', 'screening', 'discovered', 'discovered',
const active = queue.listActive();
expect(active.map((candidate) => candidate.postId)).toEqual([
'screening', 'ready', 'rejected', 'waiting',
]);
expect(queue.listActive().map((candidate) => candidate.postId)).not.toContain(rejected.postId);
expect(queue.getOldestActiveAt()).toBe(1_000);