Skip to content

📝 docs(infra): add multi-region architecture ADRs (126-132) - #393

Draft
sodre wants to merge 3 commits into
mainfrom
docs/adr-multi-region
Draft

sodre wants to merge 3 commits into
mainfrom
docs/adr-multi-region

Conversation

@sodre

@sodre sodre commented Feb 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Add seven proposed ADRs documenting the multi-region rate limiting architecture
  • ADR-126: Independent regional tables (reject Global Tables)
  • ADR-127: S3-based cross-region sync exchange
  • ADR-128: Quota enforcement via entity config overrides
  • ADR-129: Trigger-based sync writes (exhaustion + drift)
  • ADR-130: Per-region sync Lambda (symmetric, no coordinator)
  • ADR-131: TTL on sync-written config records (extends ADR-119)
  • ADR-132: Sync config ownership via TTL presence

Test plan

  • Review each ADR for technical accuracy and consistency
  • Verify ADR numbering does not conflict with existing ADRs
  • Confirm all ADRs have "Proposed" status

🤖 Generated with Claude Code

Seven proposed ADRs documenting the multi-region rate limiting architecture:
- ADR-123: Independent regional tables (reject Global Tables)
- ADR-124: S3-based cross-region sync exchange
- ADR-125: Quota enforcement via entity config overrides
- ADR-126: Trigger-based sync writes (exhaustion + drift)
- ADR-127: Per-region sync Lambda (symmetric, no coordinator)
- ADR-128: TTL on sync-written config records (extends ADR-119)
- ADR-129: Sync config ownership via TTL presence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sodre sodre added this to the v0.10.0 milestone Feb 15, 2026
@sodre sodre added the area/limiter Core rate limiting logic label Feb 15, 2026
@codecov

codecov Bot commented Feb 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 29.14%. Comparing base (0858e59) to head (06653b2).
⚠️ Report is 166 commits behind head on main.
✅ All tests successful. No failed tests found.

❗ There is a different number of reports uploaded between BASE (0858e59) and HEAD (06653b2). Click for more details.

HEAD has 3 uploads less than BASE
Flag BASE (0858e59) HEAD (06653b2)
unit 1 0
e2e 1 0
integration 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #393       +/-   ##
===========================================
- Coverage   91.87%   29.14%   -62.74%     
===========================================
  Files          33       37        +4     
  Lines        7435     7854      +419     
===========================================
- Hits         6831     2289     -4542     
- Misses        604     5565     +4961     
Flag Coverage Δ
doctest 29.14% <ø> (+1.15%) ⬆️
e2e ?
integration ?
unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@sodre sodre modified the milestones: v0.10.0, v2.0.0 Feb 15, 2026
ADR-123 was assigned to local-secondary-indexes on main after this
branch was created, so the multi-region series collided starting at
123. Shift the whole series up by 3 (123→126 … 129→132) to the next
free block, updating titles and all internal cross-references.
References to existing ADRs (105, 114, 117, 118, 119, 122) are
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sodre sodre changed the title 📝 docs(infra): add multi-region architecture ADRs (123-129) 📝 docs(infra): add multi-region architecture ADRs (126-132) Jun 12, 2026
…→ 124)

This branch forked from main after both ADR-121 files existed, so it
carried the same duplicate-121 collision. Apply the identical fix used
on main (#422): rename policy-rename-clarity 121 → 124 so the branch is
self-consistent and merges cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sodre added a commit that referenced this pull request Jun 12, 2026
…refs (#422)

## Summary

This PR collects four commits of ADR consistency cleanup on `main`:

1. **Resolve the duplicate ADR-121** — two Accepted ADRs both claimed
number 121. Native-sync keeps 121 (earlier file, referenced externally
by CLAUDE.md); the IAM policy-rename ADR is renumbered `121 → 124` (next
free number after `123-local-secondary-indexes`). Structural collision
fix only — no decision content changed, consistent with the ADR
immutability rule.
2. **Normalize ADR-121 (native-sync) status header** — it used a `##
Status` heading with the value on a separate line; normalized to the
`**Status:**` / `**Date:**` inline format mandated by ADR-000 (Date
`2026-02-02`, from the file's creation commit).
3. **Add ADRs 114-124 to the mkdocs navigation** — the nav previously
stopped at ADR-113, leaving 114-124 unreachable from the docs site.
Added thematic groups covering 114-124 and reflecting the corrected
numbering.
4. **Resolve cross-ADR consistency issues** surfaced by `/adr
consistency`:
- **ADR-107**: annotate its partial supersession by ADR-117. Two
Accepted ADRs asserted opposite IAM defaults (roles-by-default vs
policies-by-default); flag it in the status line and add a Decision note
pointing to ADR-117 (current default) and ADR-124 (policy rename) so the
reversed default is unambiguous.
- **ADR-100**: this Proposed index still presented superseded
sub-decisions (102→118, 103→122) as authoritative and used the
pre-composite `#LIMIT#` schema. Mark the superseded rows and show the
current composite `#CONFIG` keys.
- **ADR-013**: accept it. It was Proposed yet already superseded ADR-012
— an invalid state. Its decision is complete and already operationalized
as `.claude/rules/docs-parity.md`, so finalize the status.

This is the **main-branch half of a coordinated cleanup**. The other
halves renumber ADRs on open PRs so the whole sequence is
collision-free:

- **#304**: `121 → 125`
- **#393**: `123-129 → 126-132` (plus the same inherited `121 → 124`
fix)

Together these yield a collision-free ADR sequence **121-132**.

## Test plan

- [ ] `ls docs/adr/12*.md` shows no duplicate ADR numbers
- [ ] `grep -rn "121-policy-rename" docs/` returns no stale references
- [ ] ADR-124 header reads `# ADR-124: Rename IAM Policies for Clarity`
- [ ] ADR-121 header uses the `**Status:**` / `**Date:**` inline format
- [ ] `mkdocs build` reaches ADRs 114-124 from the nav
- [ ] ADR-107 status line notes the partial supersession by ADR-117
- [ ] ADR-100 marks the 102→118 / 103→122 superseded rows and shows
`#CONFIG` keys
- [ ] ADR-013 status reads Accepted

🤖 Generated with [Claude Code](https://claude.ai/code)
sodre added a commit that referenced this pull request Sep 15, 2026
## Summary

Two **Accepted** ADRs recording decisions about `reset_schedule` (the
#222 surface plan), both settled after the token bucket's continuous
refill was found to conflict with a calendar reset in a way that neither
the design doc nor the surface plan had stated.

### ADR-137 — a limit drips or resets, never both

Drip and reset are both refill mechanisms, and they were specified
independently. A limit carrying both grants roughly **twice** its
intended allowance in a period: the reset hands over a full balance, the
caller spends it, and the drip returns the same allowance again over the
rest of the period.

The reset is not at fault. It is a SET, not an ADD, so repeated or
missed edges apply once. The surplus is the drip running underneath it.

`refill_amount = 0` now means "no drip", and is valid **only** when a
`reset_schedule` is present. Pairing the two fields in validation also
makes a bucket with neither mechanism — one that drains and never
recovers — unconstructible.

An earlier draft of this ADR chose the other option: keep the
positive-rate rule and silently ignore the stored rate when a reset
exists. It was changed because a configured number that does nothing is
a trap for whoever reads the limit next. That alternative is recorded in
the ADR's Alternatives Considered.

### ADR-138 — reset covers fixed calendar windows only

A cron expression names wall-clock instants, so every entity sharing a
schedule resets together. A window anchored to an entity's own first use
would need a per-entity timestamp on the bucket — a different mechanism,
and one that would cost the property making the current design cheap:
the valid-until stamp being computable from the bucket item alone.

This ADR matters mostly for **what it excludes**. The nearest widely
deployed behaviour — a session window that starts when the caller starts
— is exactly the case not covered, and anyone reading "five-hour reset
window" will assume it. The ADR also records the cost the choice
accepts: every entity resetting at the same instant is a thundering herd
that the design does nothing to spread.

### Consistency

Both were checked against every Accepted ADR before acceptance. The only
real interaction is **ADR-136**, whose TTL rule confines the exposure —
a bucket resolving its limits from entity configuration carries no TTL
at all, so only resource- and system-level reset limits reach the
time-to-fill formula that divides by the refill rate. ADR-137 states
this, and ADR-136's claim that the formula is unchanged stays true for
every limit that drips.

### Numbering

137 and 138 because 126–132 are reserved by open PR #393 (multi-region
ADRs), and 135 is earmarked by surface-plan Task 12, which is literally
titled "ADR-135 and documentation".

### Why this lands before surface-plan Group A

Group A (Tasks 1–5) implements reset, and both decisions change what
those tasks do. Task 1 is already written and parked on an unpushed
local branch; it will need updating against ADR-137 before it goes
anywhere.

## Test plan

- [x] None runnable — documentation only, no code paths touched.
- [x] Consistency check over every Accepted ADR on the topics these
touch (refill, TTL, reset, limit validation).
- [x] Confirmed ADR numbers 137 and 138 are unused.

Refs #222

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QdVj8nPhUwTz2aNJzMFqt5
sodre added a commit that referenced this pull request Sep 15, 2026
## Summary

Surface-plan Task 12 writes to two audiences whose needs are opposite,
and said nothing about the difference. It creates
`docs/adr/135-scheduled-limits.md` and updates `CLAUDE.md` — an ADR
exists to record why a decision was taken and what was rejected, and
`CLAUDE.md` is a developer reference, so both should carry the full
rationale. It also updates `docs/guide/`, `docs/cli.md` and `docs/api/`,
which should carry none of it. Nothing in the task told the implementer
to treat those two sets differently, so the default is to write the same
prose everywhere.

**The rule now stated.** If a sentence exists to explain what used to be
true, what changed, or what an earlier design did, it does not belong in
the user-facing docs. No "previously", no "note that this no longer", no
rejected alternatives, no justification of the design. Show the API that
exists and what it does. A reader there has no history to reconcile and
did not attend the design discussion.

**The worked example, which is why the rule has teeth.** An early draft
of the scheduled-limits guide (#524) explained why
`Limit.per_day(...).with_reset_schedule(...)` is rejected under ADR-137.
No reader has ever written that line — the form they meet is the one
that exists — so the explanation teaches a wrong thing first and then
unteaches it. That case is now written into the task, so the rule is
concrete rather than an adjuration. Worth recording that the instruction
to explain the rejected form came from the coordinator's own brief to
the agent writing that guide: this is a mistake we made and corrected,
not a hypothetical one.

**Two carve-outs, so the cut does not overshoot.** The version
admonition ("new in v0.14.0") stays — it tells a reader on an older
release why something is absent. Current limitations stay too, ADR-138's
fixed-window restriction in particular, stated as a fact about the
feature rather than as a decision taken or an alternative weighed.

**A second rule: do not frame a configurable thing by one of its
values.** The reset period is whatever the cron expression says. A
section titled "daily quotas" carrying three midnight examples teaches
that quotas are a midnight feature. Task 12 now asks for the range — a
session cap resetting every few hours, a monthly plan on the 1st, a
weekly cap — with one example worked in full and the rest as one-liners.

**A stale fact corrected.** Step 1 told the implementer to verify ADR
number 135 is unclaimed and asserted that `main` "currently tops out at
134". It tops out at 138: ADR-136 (entity config bucket TTL), ADR-137 (a
limit drips or resets, never both) and ADR-138 (fixed calendar reset
windows only) all landed after the plan was written, and 135 is free
only by accident. Step 1 now says so, and adds that ADR-135 must
reference 137 and 138 rather than restate or re-decide what they
settled.

## Test plan

- [x] None runnable — a single planning document, no code paths touched.
- [x] Verified `main` tops out at ADR-138 and that ADR-135 is genuinely
unclaimed on `main` and in open PRs (#393 holds 126–132).

Refs #222
Refs #524

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QdVj8nPhUwTz2aNJzMFqt5
sodre added a commit that referenced this pull request Sep 16, 2026
## Summary

- `mkdocs.yml` was missing nav entries for exactly the four ADRs this
release is built on: ADR-135, ADR-136, ADR-137, ADR-138. PR #483 owned
`mkdocs.yml` while those ADRs were being written, so they landed on
`main` reachable only by direct URL — absent from the docs nav entirely.
- Adds all four in numeric order after ADR-134, each under its own ADR
title:
  - ADR-135 Scheduled limits are resolved at read time
  - ADR-136 Entity Configuration Determines Bucket TTL
  - ADR-137 A limit drips or resets, never both
  - ADR-138 Calendar reset covers fixed windows only
- No other files touched.

**ADRs 126-132 are deliberately NOT added.** Those numbers are reserved
by open PR #393 and the corresponding files do not exist on `main`. A
nav entry pointing at a missing file fails `mkdocs build --strict`, so
they stay out until #393 lands.

## Test plan

- [ ] `uv run mkdocs build --strict` exits 0 with zero `WARNING` /
`ERROR` lines
- [ ] All four pages render at `site/adr/<slug>/index.html`
- [ ] Nav links for the four new entries resolve from ADR-134's rendered
page

Refs #222

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QdVj8nPhUwTz2aNJzMFqt5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/limiter Core rate limiting logic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant