fix: never let a guessed threshold hold an account out of sending
The profile check was allowed to set state 'unestablished', which made isSendable() false, which made canAcceptPost() false — so a flagged account never attempted an invitation and the functional probe could never run. The one signal with real authority was gated behind the one that is guessing, and because each check pushed retryAt forward by another interval, accounts that send perfectly well were held indefinitely. Reddit does not publish the thresholds its chat gate applies, so ours are a guess and are now advisory only. Sending is held solely by a live rejection, tracked as sendingHeld on the account summary and keyed off by the attention feed, the sending state and the Accounts page. Reason strings now say "below the configured floor of N" rather than "needs N", and the Accounts page distinguishes "Sending on hold" from "Below thresholds", stating outright that the latter is our own number and the account is still sending. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
89eb4583f8
commit
3a40651930
9 files changed
+164
-71
No files matched your search
@@ -78,9 +78,9 @@ describe('profile evaluation', () => {
|
||||
|
||||
expect(verdict.established).toBe(false);
|
||||
expect(verdict.reasons).toEqual([
|
||||
'Account is 2 days old (needs 7 days)',
|
||||
'Total karma is 2 (needs 20)',
|
||||
'Comment karma is 1 (needs 5)',
|
||||
'Account is 2 days old (below the configured floor of 7 days)',
|
||||
'Total karma is 2 (below the configured floor of 20)',
|
||||
'Comment karma is 1 (below the configured floor of 5)',
|
||||
'Email address is not verified',
|
||||
]);
|
||||
});
|
||||
@@ -176,16 +176,18 @@ describe('AccountEstablishmentService', () => {
|
||||
profileCheckedAt: NOW,
|
||||
probeFailureCount: 0,
|
||||
});
|
||||
expect(record!.reasons).toContain('Account is 1 day old (needs 7 days)');
|
||||
expect(service.isSendable()).toBe(false);
|
||||
expect(record!.reasons).toContain('Account is 1 day old (below the configured floor of 7 days)');
|
||||
// A guessed threshold must never hold the account back: it has to be
|
||||
// allowed to try, or Reddit's authoritative answer can never arrive.
|
||||
expect(record!.retryAt).toBeNull();
|
||||
expect(service.isSendable()).toBe(true);
|
||||
});
|
||||
|
||||
it('clears the flag once the account has aged and earned karma', async () => {
|
||||
let current = profile({ createdAt: NOW - DAY_MS, linkKarma: 0, commentKarma: 0, totalKarma: 0 });
|
||||
const service = createService(async () => current);
|
||||
|
||||
await service.checkNow();
|
||||
expect(service.isSendable()).toBe(false);
|
||||
expect(await service.checkNow()).toMatchObject({ state: 'unestablished' });
|
||||
|
||||
now = NOW + 10 * DAY_MS;
|
||||
current = profile({ createdAt: NOW - DAY_MS, commentKarma: 30, totalKarma: 60 });
|
||||
@@ -302,17 +304,40 @@ describe('AccountEstablishmentService', () => {
|
||||
|
||||
expect(accounts.list()[0].establishment).toMatchObject({
|
||||
state: 'unestablished',
|
||||
// Below our thresholds, but nothing Reddit has refused — so not held.
|
||||
sendingHeld: false,
|
||||
accountAgeDays: 3,
|
||||
linkKarma: 1,
|
||||
commentKarma: 2,
|
||||
totalKarma: 3,
|
||||
hasVerifiedEmail: false,
|
||||
profileCheckedAt: NOW,
|
||||
retryAt: NOW + settings.profileCheckIntervalMs,
|
||||
retryAt: null,
|
||||
});
|
||||
expect(accounts.list()[0].establishment!.reasons.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('marks sending as held only while a Reddit rejection is still in force', () => {
|
||||
const accounts = new RedditAccountsService(
|
||||
new RedditAccountsRepository(db),
|
||||
{} as never,
|
||||
{ getSnapshot: () => ({ accountId: ACCOUNT_ID }) } as never,
|
||||
undefined,
|
||||
undefined,
|
||||
repository,
|
||||
);
|
||||
// The listing derives the hold against the wall clock the dashboard renders
|
||||
// in, so this account's rejection is recorded there too.
|
||||
now = Date.now();
|
||||
const service = createService(async () => profile());
|
||||
|
||||
service.recordSendFailure('account is too new');
|
||||
expect(accounts.list()[0].establishment).toMatchObject({ state: 'unestablished', sendingHeld: true });
|
||||
|
||||
service.recordSendSuccess();
|
||||
expect(accounts.list()[0].establishment).toMatchObject({ state: 'established', sendingHeld: false });
|
||||
});
|
||||
|
||||
it('skips the check while the account session is not authenticated', async () => {
|
||||
const getAccountProfile = vi.fn(async () => profile());
|
||||
const service = new AccountEstablishmentService(
|
||||
|
||||
Reference in new issue
Block a user