Skip to content

Commit dbf8713

Browse files
authored
fix: quota denial message no longer tells the agent to retry (#144)
## Summary When `/v1/assess` returns 429 `quota_exceeded` and `failOpen` / `fail_open` is off, the gate denies with `api_error` and the quota-specific `contact_merchant` instructions ("Do not retry: the same 503 will be returned until the merchant resolves the issue"). The reason carried no message, so the body fell back to the `api_error` default, "AgentScore is unreachable. This is transient: retry in a few seconds." An agent reading both got two opposite instructions in one response. The quota denial now carries its own message, `QUOTA_EXCEEDED_MESSAGE`: "AgentScore identity verification is unavailable for this merchant. Retrying will not help." Every place that builds the quota denial sets it (the typed and the untyped 429 paths). Found in a docs audit while checking the errors reference against the SDK's emitted bodies. ## Type of change - [x] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [ ] Docs, tests, or internal maintenance only ## Public API Adds the exported constant `QUOTA_EXCEEDED_MESSAGE` beside `QUOTA_EXCEEDED_INSTRUCTIONS`. The 503 body's `error.message` on the quota path changes text; its shape and status do not. No release is cut for this alone; the next release carries it. ## Test plan The quota tests now assert the message. Positive control: with the message removed from the deny sites, those tests fail; restored, they pass. Full lint, type check and test suite pass locally. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed (no README text covers this message) - [x] No secrets, credentials, or personal data in the diff or the tests
1 parent 1797587 commit dbf8713

4 files changed

Lines changed: 12 additions & 5 deletions

File tree

‎src/_response.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ const API_ERROR_INSTRUCTIONS = JSON.stringify({
7373
'Verification is temporarily unavailable. Please try again in a moment: this is a transient issue, not a problem with your account.',
7474
});
7575

76+
// The message paired with QUOTA_EXCEEDED_INSTRUCTIONS. The api_error default says the failure is
77+
// transient and worth retrying, which contradicts the contact_merchant instructions on this path.
78+
export const QUOTA_EXCEEDED_MESSAGE = 'AgentScore identity verification is unavailable for this merchant. Retrying will not help.';
79+
7680
export const QUOTA_EXCEEDED_INSTRUCTIONS = JSON.stringify({
7781
action: 'contact_merchant',
7882
steps: [

‎src/core.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
TokenExpiredError,
1010
} from '@agent-score/sdk';
1111
import { isFixableDenial } from './_denial';
12-
import { QUOTA_EXCEEDED_INSTRUCTIONS } from './_response';
12+
import { QUOTA_EXCEEDED_INSTRUCTIONS, QUOTA_EXCEEDED_MESSAGE } from './_response';
1313
import { normalizeAddress } from './address';
1414
import { TTLCache } from './cache';
1515
import type { PaymentSigner } from './signer';
@@ -864,7 +864,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore
864864
if (failOpen) return { kind: 'allow', degraded: true, infraReason: 'quota_exceeded' };
865865
return {
866866
kind: 'deny',
867-
reason: { code: 'api_error', agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS },
867+
reason: { code: 'api_error', agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS, message: QUOTA_EXCEEDED_MESSAGE },
868868
};
869869
}
870870
if (err instanceof SdkTimeoutError) {
@@ -883,7 +883,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore
883883
if (failOpen) return { kind: 'allow', degraded: true, infraReason: 'quota_exceeded' };
884884
return {
885885
kind: 'deny',
886-
reason: { code: 'api_error', agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS },
886+
reason: { code: 'api_error', agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS, message: QUOTA_EXCEEDED_MESSAGE },
887887
};
888888
}
889889
if (errName === 'TimeoutError' || errName === 'AbortError') {

‎tests/edge-cases.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ describe('error response edge cases', () => {
223223

224224
expect(next).not.toHaveBeenCalled();
225225
expect(status).toHaveBeenCalledWith(503);
226-
expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.objectContaining({ code: 'api_error' }) }));
226+
expect(json).toHaveBeenCalledWith(expect.objectContaining({
227+
error: expect.objectContaining({ code: 'api_error', message: expect.stringContaining('Retrying will not help') }),
228+
}));
227229
});
228230

229231
it('typed QuotaExceededError fails open (degraded allow) when failOpen is true', async () => {

‎tests/hono.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,9 @@ describe('Hono adapter: error paths', () => {
625625

626626
const res = await app.request('/test', { headers: { 'x-wallet-address': WALLET } });
627627
expect(res.status).toBe(503);
628-
const body = await res.json() as { error: { code: string }; agent_instructions: string };
628+
const body = await res.json() as { error: { code: string; message: string }; agent_instructions: string };
629629
expect(body.error.code).toBe('api_error');
630+
expect(body.error.message).toContain('Retrying will not help');
630631
const instructions = JSON.parse(body.agent_instructions);
631632
expect(instructions.action).toBe('contact_merchant');
632633
expect(instructions.steps[0]).toContain('merchant-side issue');

0 commit comments

Comments
 (0)