Repository navigation
test(runtime): judge each share-link password pair once in the refusal-order file, so its loop cases fit the 5000 ms default - #24704
Conversation
…l-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>
…esolveToken verifies Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
席位核对 · ACCEPT
Accepted deviations:
Out-of-scope dispositions (one line each):
Landing: through the merge queue by this seat once every check on this head reads Generated by Claude Code |
|
Landing hold · Generated by Claude Code |
Fixes #24633
Clause-②: no
What changes
One test file:
packages/runtime/src/domains/share-links-refusal-order.test.ts. ItsbeforeAllwraps theShareLinkServiceinstance's own password verifier in a memo keyed by the(password, hash)pair. The first request that presents a given pair is judged by the production verifier (scrypt at the account parameters). Every later request with the same pair replays that verdict.What stays the same:
testTimeoutchange and no// test-budget:header.The production verifier is not exported from
@objectstack/plugin-sharing, so the memo reads it off the instance. If that member is renamed,beforeAllthrows with a message that names the member. It does not quietly fall back to paying for every call.Measured cause: production scrypt verifications, not the resolves
resolveTokencalls the verifier once for each password presented to a live, password-protected link:signed_inlink, once the caller is signed in;emaillink, whatever email was sent (the same-work rule from security(sharing): the share-link resolve gate may answer an email-audience refusal at a cost that depends on caller input — detail withheld pending maintainer #23474).Each call derives a scrypt key at N=16384, r=16. I counted the calls with a temporary instrumented copy of the file. The copy wrapped the instance's verifier and hasher with counters keyed by
expect.getState().currentTestName. It was deleted after the run and never committed (git statusclean after each run).eadea5720)a70fd811f)hashPassword, inbeforeAll):334no-password-oracle loop (13 resolves), per mount:346allowlist-membership loop (8 resolves), per mount:356unknown-token loop (18 resolves), per mountThe 6 distinct pairs are the 3 live protected links (
signed_in + password,public + password,email + password), each with a wrong password and with the correct one. The revoked link is refused before verification. A verdict depends only on its pair, so replaying it changes no answer.Readings
CI, before. Run
39298888283:118219765573(Test Core (6/6)): the card's red.:356, dispatcher twin, failed withTest timed out in 5000ms.118235895669: green on the same head.Test Coreaggregate job118247422913prints the suite-ceiling report into its log. It lists this file as@objectstack/runtime's slowest:34.84s src/domains/share-links-refusal-order.test.ts [local]. That is over the 10 s line in AGENTS.md (Build & Test).test-core-timings-6-of-6artifact could not be read from here. The artifact download redirects to blob storage, and this container's egress proxy answers that redirect 403.Local runs. The box has 4 vCPUs and is shared with sibling agents. Every run took the verify lock. Durations come from vitest's JSON reporter. The "file" figure is vitest's module line for the file.
eadea5720)810176fb8,a70fd811f)--maxWorkers=2:334, idle (plugin route / twin):346, idle:356, idle--maxWorkers=1Test timed out in 5000ms::356plugin route 5050 ms,:356twin 5002 ms,:334twin 5001 ms.:334plugin route passed at 4808 ms. File 44531 ms.Notes on these readings:
:334is the next case to go.a70fd811f, is the third one (1296 ms).CI, after: NOT MEASURED until this PR's
Test Coreruns. Two projections, both under 10 s:Why not the suggested per-case timeout, and why there is no
test-budgetheaderA per-case timeout leaves the file over the 10 s line. It would keep 4 to 10 derivations inside each loop case, and the file would stay near 35 s in CI. AGENTS.md says a file over 10 s of wall is its lane's card, unless the file declares a measured budget with its class.
No class fits this file, so it cannot declare a budget.
TEST_BUDGET_CLASSESinscripts/measure-test-shard-timings.mjsis a closed list of three classes. Its reader refuses any other class:spawns the real producer once per single-cause fixture;whole-package import;compiles the package type graph once.None of the three describes key derivations.
So the change removes the cost instead. The file now projects under the line, and the loop cases no longer need a budget. The slowest case under contention read 638 ms, 12.8% of the 5000 ms default.
The precedents were read from their landed diffs.
b9222dc70(test(plugin-sharing): size the three scrypt cases near vitest's 5000 ms default to their measured work #22426) sized per-case budgets for files whose subject is the hashing itself, and its rule was never to lower the scrypt parameters. That rule holds here: every verdict still comes from production scrypt at the production parameters.7b6c65290(test(rest): pay the state route's objectql load at collection, so the multi-kernel case stops timing out on the hourly run #21925, for test(rest):meta-state-route-engine-outage.test.ts's multi-kernel wiring case times out at vitest's 5000 ms default under the hourly full run, and has turnedmain's hourly run red three times in two days #21920) moved a module load out of the clocked window. The shape does not transfer, because the cost here is the cases' own derivations.closedevent carries no commit, and no commit onorigin/maincites it. As a control, the same grep findsb9222dc70for test(plugin-sharing): size the three scrypt cases near vitest's 5000 ms default to their measured work #22426.Mechanism assumptions, checked
Tests and gates
All at
a70fd811f(git rev-parse --short HEAD), the PR head.pnpm --filter '@objectstack/runtime^...' buildunder the verify lock:VERDICT command-exit 0.pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 src/domains/share-links-refusal-order.test.ts, under the lock:Tests 60 passed (60), file line 1296 ms.pnpm --filter @objectstack/runtime typecheck, under the lock:VERDICT command-exit 0. This includescheck:test-typecheck: OK. The file is intsconfig.test.json's program:tsc --listFilesOnlycounts it once.node scripts/pm/dispatch-gates.mjs --commandsderived 53 commands. Results:51 exited 0.
2 exited 3 (
PREREQUISITE NOT MET), so they are NOT MEASURED here:node scripts/check-plugin-teardown-shape.mjs --self-testneeds an unshallowed clone;pnpm check:dual-build-cjs-loadsneeds the whole workspace'sdist.CI runs both.
--ranreconciliation:53 derived famil(ies) accounted for — 51 run, 2 NOT-MEASURED,0 UNRUN.pnpm lint(eslint over the repo): exit 0, no findings.@objectstack/runtimelocalsuite was not run here. It reads about 1000 s in CI. The diff is one self-contained test file that shares no state with its siblings.Test Coreruns the full suite.No changeset (
skip-changeset)Only a
*.test.tsfile changes.@objectstack/runtime'sfiles[]isdist,README.mdandCHANGELOG.md, and tsup's only entry issrc/index.ts. I builtdistafter the final commit and searched it:productionVerify: 0 hits;handleShareLinksRequest: 2 hits.Acceptance notes
ShareLinkService'sverifyPasswordmember, which isprivate readonlyin TypeScript and is not exported.beforeAllthrows and names it.:334and:346are implied by:356.:356proves every refused cell equals the unknown-token answer, and the other two cases' references are among those cells. This was not acted on: merging or deleting cases is held off, and none of the three costs measurable time now.src/domains/share-links-messages-audience.test.tsread 13.51 s in the same CI report. It mints with the same production hasher over a password matrix, so it probably has the same cause. It is not touched here: it is outside this card's file surface.Generated by Claude Code