Skip to content

Rate limits: design limits without Redis; registration proof-of-work replaces the per-IP quota - #68

Merged
Tailor-AUS merged 3 commits into
rehome-reviewfrom
agent/open-graph-limits
Sep 18, 2026
Merged

Tailor-AUS merged 3 commits into
rehome-reviewfrom
agent/open-graph-limits

Conversation

@Tailor-AUS

@Tailor-AUS Tailor-AUS commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Refs TailorAU/tailor-group#7 (step 1 of 3). Base is rehome-review; main is untouched.

What was wrong

pact.tailor.au runs one pact-web replica with no Redis (/api/healthredis: fallback-in-memory). src/lib/rate-limit.ts treated 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 3 the worst case under scale-out is 3× per key; RATE_LIMIT_REPLICA_HINT=N divides the limits back down if scale-out becomes routine. One warn log 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:

  1. without pow428 { pow: { challenge, algorithm, bits, expiresIn, howto } } — HMAC-signed, stateless, 10-minute TTL;
  2. client finds a nonce with sha256(challenge + ":" + nonce) having ≥ bits leading zero bits (default 20 ≈ 1 s CPU; REGISTRATION_POW_BITS);
  3. re-POST with pow: { challenge, nonce } → 201. Each challenge is single-use (in-memory ledger, Redis SET NX when 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=off exists for local dev only.

Per-key write limits everywhere. lib/write-limit.ts puts join, done, dependencies (POST+DELETE), verify, approve/reject/object, escalate, bounty, salience, constraints, intents on the same 30/min per-key write window that topics/proposals/vote/legislation-propose already used.

Clients. scripts/pact_pow.py (stdlib solver + register() helper). _defence_seed_helpers.py and propose_bcc_council_instruments.py use it, and the seed helpers now default to https://pact.tailor.au (the old source.tailor.au default 308s and requests downgrades 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.py pins workflow files that live in the tailor-app monorepo; in this repo the 10 tests errored on a missing file. They now skipUnless the 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. eslint on changed files: 0 errors (one pre-existing unused-import warning in bounty/route.ts, untouched).
  • python -m unittest discover -s tests in scripts/: 60 tests, OK (11 skipped, was 10 errors).
  • Python solver at 20 bits: sub-second on this laptop.

Not in this PR

  • Legacy seed_fresh.py / seed_institutional.py / seed_legislation.py / bootstrap_*.py still register the old way and target source.tailor.au; they are not part of the seed corpora for step 2 and will 428 until they adopt pact_pow.register.
  • public/.well-known/agent-card.json still advertises source.tailor.au for registration.
  • The axiom-keys 3/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/register returns 428 with a signed challenge when pow is missing; clients solve and re-POST. Flood control is a 60/hour per-address register-ip window, a tunable daily circuit breaker (MAX_DAILY_REGISTRATIONS, default 500), and existing global limits. New registration-pow.ts, env knobs in .env.example, OpenAPI/docs updates, and scripts/pact_pow.py for Python callers; seed helpers default to https://pact.tailor.au and support SEED_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_HINT divides 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-token shares the registration flood backstop.

CI/docs: pr-check.yml actionlint 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.

…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>
@cursor

cursor Bot commented Sep 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

TailorAI-AUS and others added 2 commits September 18, 2026 13:09
…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>
@Tailor-AUS

Copy link
Copy Markdown
Contributor Author

Two follow-up commits on this branch: SEED_DRY_RUN=1 for the #1137 seed corpora (GET-only plan; dry-run against pact.tailor.au today shows 30 topics to create, 0 present, 25 edges pending those topics) and a fix to pr-check.yml's actionlint step, which listed five monorepo workflow files that do not exist here and exited 3 on every PR — that, not this change, was the red "Source tests and build" check. #69 will show the same red at that step until this merges and it is updated from base.

@cursor

cursor Bot commented Sep 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@Tailor-AUS
Tailor-AUS merged commit 1657323 into rehome-review Sep 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants