Skip to content

Commit 810176f

Browse files
committed
test(runtime): judge each share-link password pair once in the refusal-order file, so its loop cases fit the default budget
The refusal-order file's wall time was the production scrypt verifier, called once per password-bearing request: 70 verifications plus 4 mints per run. They hold 6 distinct (password, hash) pairs, so the verifier's own verdict for each pair is memoised and replayed. Every request is still made, every matrix cell still asserted, and the service keeps its default password members. Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent eadea57 commit 810176f

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

‎packages/runtime/src/domains/share-links-refusal-order.test.ts‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@
5757
* probes recover it the fail-closed way), ONE `ShareLinkService` with the
5858
* production password hasher, and the password sent the way the console's
5959
* landing page sends it: the `X-Share-Password` header. Refusals are read as
60-
* the ADR-0112 envelope's `status` + `error.code`.
60+
* the ADR-0112 envelope's `status` + `error.code`. Every password verdict is
61+
* the production verifier's own; a pair it already judged is answered from
62+
* a memo of that verdict (see `beforeAll`).
6163
*/
6264

6365
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
@@ -170,6 +172,35 @@ beforeAll(async () => {
170172
await engine.insert(OBJECT, { id: RECORD, title: 'Shared doc' }, { context: { isSystem: true } } as any);
171173

172174
const svc = new ShareLinkService({ engine: engine as any });
175+
// [#24633] One scrypt verification per distinct (password, hash) pair, not
176+
// one per request. The production verifier derives a key at the account
177+
// parameters (scrypt N=16384, r=16) on every call, and `resolveToken` calls
178+
// it for every password presented to a protected link, an `email` one
179+
// included whatever email was sent. Counted at `eadea5720`: 70 calls, plus
180+
// the 4 mints below, and they were the file's wall time. It read 34.84 s in
181+
// CI (run 39298888283, Test Core 6/6, attempt 2), and the loop case with
182+
// 10 calls timed out at vitest's 5000 ms default on attempt 1. The calls
183+
// hold only 6 distinct pairs (3 protected live links x a wrong and the
184+
// correct password), and a verdict is a pure function of its pair, so each
185+
// pair is judged once by the real verifier and its verdict replayed.
186+
// Every request is still made, so every answer is still a real request's,
187+
// and the service is still built with both password members at their
188+
// defaults, as production builds it (so its legacy-hash upgrade stays on;
189+
// no row here is legacy). The memo wraps the instance's own verifier,
190+
// which is not exported. If that member moves, this throws rather than
191+
// leave the file paying for every call again.
192+
const productionVerify: unknown = (svc as any).verifyPassword;
193+
if (typeof productionVerify !== 'function') {
194+
throw new Error('ShareLinkService no longer keeps its password verifier as `verifyPassword`; re-point the memo below');
195+
}
196+
const verdicts = new Map<string, Map<string, Promise<boolean>>>();
197+
(svc as any).verifyPassword = (plain: string, hash: string): Promise<boolean> => {
198+
let byPlain = verdicts.get(hash);
199+
if (!byPlain) verdicts.set(hash, (byPlain = new Map()));
200+
let verdict = byPlain.get(plain);
201+
if (!verdict) byPlain.set(plain, (verdict = (productionVerify as (p: string, h: string) => Promise<boolean>)(plain, hash)));
202+
return verdict;
203+
};
173204
const creator = { isSystem: true, userId: 'usr_creator' } as any;
174205
const mint = async (audience: 'public' | 'signed_in' | 'email', password?: string) =>
175206
svc.createLink(

0 commit comments

Comments
 (0)