From 810176fb8e6c4196acc861d79fa4fb4768326557 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 23:40:37 +0000 Subject: [PATCH 1/2] 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 --- .../domains/share-links-refusal-order.test.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/domains/share-links-refusal-order.test.ts b/packages/runtime/src/domains/share-links-refusal-order.test.ts index 895f6a62b5b..0a737b0919d 100644 --- a/packages/runtime/src/domains/share-links-refusal-order.test.ts +++ b/packages/runtime/src/domains/share-links-refusal-order.test.ts @@ -57,7 +57,9 @@ * probes recover it the fail-closed way), ONE `ShareLinkService` with the * production password hasher, and the password sent the way the console's * landing page sends it: the `X-Share-Password` header. Refusals are read as - * the ADR-0112 envelope's `status` + `error.code`. + * the ADR-0112 envelope's `status` + `error.code`. Every password verdict is + * the production verifier's own; a pair it already judged is answered from + * a memo of that verdict (see `beforeAll`). */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; @@ -170,6 +172,35 @@ beforeAll(async () => { await engine.insert(OBJECT, { id: RECORD, title: 'Shared doc' }, { context: { isSystem: true } } as any); const svc = new ShareLinkService({ engine: engine as any }); + // [#24633] One scrypt verification per distinct (password, hash) pair, not + // one per request. The production verifier derives a key at the account + // parameters (scrypt N=16384, r=16) on every call, and `resolveToken` calls + // it for every password presented to a protected link, an `email` one + // included whatever email was sent. Counted at `eadea5720`: 70 calls, plus + // the 4 mints below, and they were the file's wall time. It read 34.84 s in + // CI (run 39298888283, Test Core 6/6, attempt 2), and the loop case with + // 10 calls timed out at vitest's 5000 ms default on attempt 1. The calls + // hold only 6 distinct pairs (3 protected live links x a wrong and the + // correct password), and a verdict is a pure function of its pair, so each + // pair is judged once by the real verifier and its verdict replayed. + // Every request is still made, so every answer is still a real request's, + // and the service is still built with both password members at their + // defaults, as production builds it (so its legacy-hash upgrade stays on; + // no row here is legacy). The memo wraps the instance's own verifier, + // which is not exported. If that member moves, this throws rather than + // leave the file paying for every call again. + const productionVerify: unknown = (svc as any).verifyPassword; + if (typeof productionVerify !== 'function') { + throw new Error('ShareLinkService no longer keeps its password verifier as `verifyPassword`; re-point the memo below'); + } + const verdicts = new Map>>(); + (svc as any).verifyPassword = (plain: string, hash: string): Promise => { + let byPlain = verdicts.get(hash); + if (!byPlain) verdicts.set(hash, (byPlain = new Map())); + let verdict = byPlain.get(plain); + if (!verdict) byPlain.set(plain, (verdict = (productionVerify as (p: string, h: string) => Promise)(plain, hash))); + return verdict; + }; const creator = { isSystem: true, userId: 'usr_creator' } as any; const mint = async (audience: 'public' | 'signed_in' | 'email', password?: string) => svc.createLink( From a70fd811ff4f756f4f66e92a9c08da499cdab31e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 23:52:13 +0000 Subject: [PATCH 2/2] test(runtime): name the memo's verifier type and state exactly when resolveToken verifies Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude --- .../src/domains/share-links-refusal-order.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/domains/share-links-refusal-order.test.ts b/packages/runtime/src/domains/share-links-refusal-order.test.ts index 0a737b0919d..7041251ea14 100644 --- a/packages/runtime/src/domains/share-links-refusal-order.test.ts +++ b/packages/runtime/src/domains/share-links-refusal-order.test.ts @@ -175,8 +175,9 @@ beforeAll(async () => { // [#24633] One scrypt verification per distinct (password, hash) pair, not // one per request. The production verifier derives a key at the account // parameters (scrypt N=16384, r=16) on every call, and `resolveToken` calls - // it for every password presented to a protected link, an `email` one - // included whatever email was sent. Counted at `eadea5720`: 70 calls, plus + // it for each password presented to a live protected link, once a + // `signed_in` link's caller is signed in, and on an `email` link whatever + // email was sent. Counted at `eadea5720`: 70 calls, plus // the 4 mints below, and they were the file's wall time. It read 34.84 s in // CI (run 39298888283, Test Core 6/6, attempt 2), and the loop case with // 10 calls timed out at vitest's 5000 ms default on attempt 1. The calls @@ -189,18 +190,20 @@ beforeAll(async () => { // no row here is legacy). The memo wraps the instance's own verifier, // which is not exported. If that member moves, this throws rather than // leave the file paying for every call again. + type Verify = (plain: string, hash: string) => Promise; const productionVerify: unknown = (svc as any).verifyPassword; if (typeof productionVerify !== 'function') { throw new Error('ShareLinkService no longer keeps its password verifier as `verifyPassword`; re-point the memo below'); } const verdicts = new Map>>(); - (svc as any).verifyPassword = (plain: string, hash: string): Promise => { + const memoVerify: Verify = (plain, hash) => { let byPlain = verdicts.get(hash); if (!byPlain) verdicts.set(hash, (byPlain = new Map())); let verdict = byPlain.get(plain); - if (!verdict) byPlain.set(plain, (verdict = (productionVerify as (p: string, h: string) => Promise)(plain, hash))); + if (!verdict) byPlain.set(plain, (verdict = (productionVerify as Verify)(plain, hash))); return verdict; }; + (svc as any).verifyPassword = memoVerify; const creator = { isSystem: true, userId: 'usr_creator' } as any; const mint = async (audience: 'public' | 'signed_in' | 'email', password?: string) => svc.createLink(