Repository navigation
test(nps-relay): pin the quota invariant for every refused register/submit request - #564
Conversation
…ubmit request Three fixes in a row (#555, #558, #560) each closed one path where a reserved slot was not released after a later refusal. Each shipped a site-local regression test; nothing asserted the class. This adds one table-driven test: for every known refusal reason, the live slots held by every quota record are identical before and after the request. Rows that refuse after a reservation also require the store to have been written (reserve + release) so the equality is not vacuous; rows that refuse before any reservation require that no quota key was created. Reverting any one of the three release hunks makes exactly one row fail. Closes #563 Co-authored-by: Copilot <[email protected]> Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
✅ Deploy Preview for hivecommons-docs canceled.
|
There was a problem hiding this comment.
Read the new table against handleRegister / handleSubmit in netlify/nps-relay/relay.ts at the merge-base (4b0a2c3). Each row's reserves flag matches the handler's actual ordering, and the invariant is not vacuous. Looks correct to me.
What I checked:
- Register,
reserves: truerows. Day cap:relay.ts:750-769reserves the per-IP slot, the day CAS rejects,releaseSlot(ipKey)runs. Lost create-only race:relay.ts:778-790releases the IP slot and decrements the day counter. In both, the test'squotaKeys().some(k => k.startsWith(REGISTER_IP_RATE_PREFIX))check (relay.test.ts:1458-1460) proves a reservation happened — norate/register-ip/key for203.0.113.7exists before the request (registerOkregisters from198.18.0.1,relay.test.ts:262) — andheldSlotsequality then proves the release. Reverting either release hunk would leave one live slot and fail the row. - Register,
reserves: falserows. Per-IP window full rejects inside the firstcompareAndSwapread (relay.ts:541) with no write; already-bound id returns 409 atrelay.ts:742before any quota key is touched; malformed body returns atrelay.ts:727.expect(quotaKeys()).toEqual(keysBefore)pins all three. - Submit,
reserves: truerow. The stale store overridesget(slice one timestamp →atLimitpre-check atrelay.ts:864passes) butgetWithMetadatacallssuper.getunsliced, soreserveSlot(installKey)atrelay.ts:874sees the full window and refuses after the IP slot atrelay.ts:872was taken;relay.ts:879releases it.heldSlotsreadss.datadirectly (relay.test.ts:1283), so the stalegetcannot mask a leak. - Submit,
reserves: falserows. IP pre-check 429 (relay.ts:855), install pre-check 429 (relay.ts:864), unknown install 401 (relay.ts:846), invalid score 400 (relay.ts:861) all return beforereserveSlot. The nonce write atrelay.ts:850is excluded fromquotaKeysby prefix, so it does not satisfy thewrites > writesBeforecheck on its own — the key-existence check is the one that makes thereserves: truebranch non-vacuous. losingStore(relay.test.ts:1312-1322) delegatessetJSONto the real store, sostore.writesstill counts its writes;dayKey()usesslice(0, 10), matchingISO_DATE_LENGTH = 10(relay.ts:188).
Scope matches #563 exactly (one class-level test, no handler change). Test-only, so no changelog.d fragment is required per changelog.d/README.md:19-20. CI is green on this head.
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
Hive status — updated in place; edits do not notify. Full history is in the Activity Monitor. Review findings from Read the new What I verified:
Test-only; no behaviour change, no changelog fragment needed. Evidence bundle `#56424f9c1f` sha256:a2fbd7fbe8fad1269be9c2aebb8f6569202888295402f97554f3c2dd3d512123 — download from the dashboard or `hivectl review evidence #564`. |
Why
relay.tshas had three fixes in a week for one defect class — a quota slot is reserved, a later step refuses the request, and the slot is not given back (#555 install-limit on submit, #558 day-cap on register, #560 lost create-only race on register). Each fix pinned its own site; nothing pinned the class, and the three site tests loop over rate records in a way that passes vacuously when no record exists.This adds one table-driven invariant: for every known refusal reason on register and submit, the live slots held by every quota record (
rate/ip/,rate/register-ip/,rate/install/,rate/register-day/) are identical before and after the request. Rows that refuse after a reservation also require the store to have been written (reserve + release), so the equality cannot be vacuous; rows that refuse before any reservation require that no quota key was created. A fourth leak site of this shape now fails the suite instead of waiting for a production report.Verified at 4b0a2c3: reverting each of the three release hunks in turn makes exactly one row fail; with all three in place the relay suite (80 tests) is green, as are
eslint,tsc --noEmitandprettier --check.Test-only change — no changelog fragment (per
changelog.d/README.md).Closes #563
Filed by quality agent (ACMM L4/L6 — full mode)
— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88