Rate limits: design limits without Redis; registration proof-of-work replaces the per-IP quota - #68
Conversation
…work replaces the per-IP quota (refs TailorAU/tailor-group#7) The knowledge graph runs on one replica with no Redis, so the in-memory limiter IS production. It clamped every window to 10% of design: 12 reads/min, 3 writes/min and one registration an hour. That clamp is gone; the in-memory limiter enforces the design limits per replica, with RATE_LIMIT_REPLICA_HINT to divide them under scale-out. Registration is open behind a cost instead of a quota: POST /api/pact/register answers 428 with an HMAC-signed SHA-256 challenge (default 20 leading zero bits, ~1 s CPU), accepts the solved nonce once (single-use ledger, in memory or Redis SET NX), and keeps a 60/hour per-address flood backstop plus an env-tunable daily circuit breaker. join-token, which can also mint an identity, shares the backstop. Every authenticated PACT mutation now draws from the same 30/min per-key write window via lib/write-limit.ts — join, done, dependencies, verify, approve/reject/object, escalate, bounty, salience, constraints and intents were unmetered. Clients: scripts/pact_pow.py solves the challenge; the #1137 seed helpers and the BCC propose tool use it and default to https://pact.tailor.au (source.tailor.au 308s and requests downgrades the re-POST to GET). Tests: rate-limit (design limits, replica hint), registration-pow (issue, solve, replay, expiry, tamper, forged difficulty), register route (428, 201, replay, backstop, breaker, off switch). The tailor-app-only workflow tests under scripts/tests now skip when the workflow files are absent instead of erroring in this repo's PR check. No new Azure resources. Redis remains the right answer for a multi-replica steady state and is a Knox decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2e8bfdf2-8864-42f4-a662-435efc78ded4) |
…ore any write (refs TailorAU/tailor-group#7) After tailor-app#5581 (a seed re-run replaced the reviewed Planning Act 2016 with a 230-character stub) no seed runs blind. With SEED_DRY_RUN=1 the three topic seeds resolve every title against the live list endpoint and print CREATE / EXISTS without registering an agent; the dependency seed resolves its 25 edges and checks each against the child's current dependency list. Zero POSTs in either path. Plan against pact.tailor.au on 2026-09-18: 30 topics to create (12 AU defence, 10 US defence, 8 critical minerals), 0 present; 25 edges unresolvable until those topics exist. The apply is a data change that runs only on Knox's go. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…AU/tailor-group#7)
pr-check.yml's lint step still named the monorepo's
source-legislation-ingest.yml, source-pr-check.yml, cron-source.yml,
cd-source.yml and infra-source-setup.yml, none of which tailor-app#5949
re-rooted here, so actionlint exited 3 ("no such file or directory") on
every PR to rehome-review — PR #68 and #69 both red at this step with no
finding of their own. Lint pr-check.yml and cd-kg.yml instead, keeping
the two narrow ignores.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Two follow-up commits on this branch: |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_8a6e79e8-bfc5-4e89-b4e4-83528de23fcd) |
Refs TailorAU/tailor-group#7 (step 1 of 3). Base is
rehome-review;mainis untouched.What was wrong
pact.tailor.auruns onepact-webreplica with no Redis (/api/health→redis: fallback-in-memory).src/lib/rate-limit.tstreated that as an emergency and clamped every window to 10% of design: 12 reads/min, 3 writes/min, 1 registration/hour (3/hour per IP × 0.1, floored to 1) and 20/min global. A 30-agent seed run needed more than a day; any shared NAT was locked out.What this does
Design limits without Redis. The in-memory limiter is authoritative at the design limits (120 reads/min, 30 writes/min per key, 200/min global). Each replica counts alone, so with
--max-replicas 3the worst case under scale-out is 3× per key;RATE_LIMIT_REPLICA_HINT=Ndivides the limits back down if scale-out becomes routine. Onewarnlog line records the posture. No new Azure resources; Redis stays a Knox decision.Registration: cost, not quota. The 3/hour-per-IP window is retired.
POST /api/pact/register:pow→ 428{ pow: { challenge, algorithm, bits, expiresIn, howto } }— HMAC-signed, stateless, 10-minute TTL;sha256(challenge + ":" + nonce)having ≥bitsleading zero bits (default 20 ≈ 1 s CPU;REGISTRATION_POW_BITS);pow: { challenge, nonce }→ 201. Each challenge is single-use (in-memory ledger, RedisSET NXwhen available); a bad nonce does not burn the challenge.Abuse control retained: a 60/hour per-address flood backstop (
register-ip), the 200/min global window, and an env-tunable daily circuit breaker (MAX_DAILY_REGISTRATIONS, default 500, was a hard-coded 100).join-token, which can also mint an identity, shares the backstop.REGISTRATION_POW=offexists for local dev only.Per-key write limits everywhere.
lib/write-limit.tsputsjoin,done,dependencies(POST+DELETE),verify,approve/reject/object,escalate,bounty,salience,constraints,intentson the same 30/min per-keywritewindow that topics/proposals/vote/legislation-propose already used.Clients.
scripts/pact_pow.py(stdlib solver +register()helper)._defence_seed_helpers.pyandpropose_bcc_council_instruments.pyuse it, and the seed helpers now default tohttps://pact.tailor.au(the oldsource.tailor.audefault 308s andrequestsdowngrades the re-POST to GET).GET /api/pact/register,public/openapi.json, README,docs/SECURITY.md,.env.example, CHANGELOG updated.CI hygiene.
scripts/tests/test_source_ingest_workflows.pypins workflow files that live in the tailor-app monorepo; in this repo the 10 tests errored on a missing file. They nowskipUnlessthe file exists. Nothing else in that suite changed.Evidence
npm test: 46 files, 757 tests pass (was 43 / 732). New:rate-limit.test.ts,registration-pow.test.ts,register/route.test.ts.npm run build: clean.npx tsc --noEmit: clean.eslinton changed files: 0 errors (one pre-existing unused-import warning inbounty/route.ts, untouched).python -m unittest discover -s testsinscripts/: 60 tests, OK (11 skipped, was 10 errors).Not in this PR
seed_fresh.py/seed_institutional.py/seed_legislation.py/bootstrap_*.pystill register the old way and targetsource.tailor.au; they are not part of the seed corpora for step 2 and will 428 until they adoptpact_pow.register.public/.well-known/agent-card.jsonstill advertisessource.tailor.aufor registration.axiom-keys3/hour-per-IP window is unchanged (it was never clamped by design, only by the 10% rule this PR removes).🤖 Generated with Claude Code
Note
Medium Risk
Changes public registration contract (428/PoW) and broadens effective rate limits on single-replica deployments; abuse posture shifts from IP quotas to PoW plus per-key write caps.
Overview
Open registration now uses a SHA-256 proof-of-work instead of a tight per-IP quota.
POST /api/pact/registerreturns 428 with a signed challenge whenpowis missing; clients solve and re-POST. Flood control is a 60/hour per-addressregister-ipwindow, a tunable daily circuit breaker (MAX_DAILY_REGISTRATIONS, default 500), and existing global limits. Newregistration-pow.ts, env knobs in.env.example, OpenAPI/docs updates, andscripts/pact_pow.pyfor Python callers; seed helpers default tohttps://pact.tailor.auand supportSEED_DRY_RUN.Rate limiting without Redis no longer clamps to 10% of design limits in production. The in-memory path enforces full design limits (120 reads/min, 30 writes/min per key, etc.); optional
RATE_LIMIT_REPLICA_HINTdivides limits when running multiple replicas without Redis.Write metering is extended via
write-limit.ts: previously unmetered PACT mutations (join,done,dependencies, proposals lifecycle,verify,escalate,bounty,salience,constraints,intents) now share the 30/min per-agent-key write window.join-tokenshares the registration flood backstop.CI/docs:
pr-check.ymlactionlint targets only workflows in this repo; monorepo workflow tests skip when files are absent.Reviewed by Cursor Bugbot for commit 74e1971. Bugbot is set up for automated code reviews on this repo. Configure here.